diff --git a/.github/release-notes/v1.12.0.md b/.github/release-notes/v1.12.0.md new file mode 100644 index 0000000..b758857 --- /dev/null +++ b/.github/release-notes/v1.12.0.md @@ -0,0 +1,55 @@ +The generated tier catches up with the API's 1.10 line — the OAuth +authorization server, reconciling items on the close, and the removal that +came with them. This release names that removal, and nothing on the stable +tier changes shape. + +## Removed: the REST MCP tool surface + +`robosystems_client.api.mcp.list_mcp_tools` and `call_mcp_tool`, with the +models `MCPToolCall`, `MCPToolCallArguments`, `MCPToolsResponse` and +`MCPToolsResponseToolsItem`, are gone. The API removed +`GET /v1/graphs/{graph_id}/mcp/tools` and `POST …/mcp/call-tool` in 1.10.2: +MCP clients speak the Streamable HTTP transport directly — +`POST /v1/graphs/{graph_id}/mcp` with the same `X-API-Key`, or the OAuth-only +`POST /v1/mcp` — and thirty days of production traffic showed no caller on the +REST pair. The facades never wrapped them and the integration template's emit +path never imported them, so this is a generated-tier removal riding a minor. +If you did call them, the replacement is a JSON-RPC `tools/list` or +`tools/call` against the same graph URL. + +## New: connected apps + +`api.user.list_user_o_auth_grants` and `revoke_user_o_auth_grant` +(`GET` / `DELETE /v1/user/oauth/grants`), with `OAuthGrantInfo` and +`OAuthGrantsResponse`. A grant is one OAuth consent — one MCP client on one +graph. The listing shows the client, the graph it reaches, the MCP URL its +tokens are bound to, and when it was last used; revoking one kills every token +minted from it, so the client fails at its next call and has to ask the user +again. Until now the only way to revoke a connection was a password change, +which revoked all of them. + +## New: reconciling items + +`api.extensions_robo_ledger.preview_reconciling_item` and +`resolve_reconciling_item` +(`POST /extensions/roboledger/{graph_id}/operations/preview-reconciling-item` +and `…/resolve-reconciling-item`), with their request, plan and response +models. A reconciling item is a posted event whose source payload changed +afterwards — a difference between the books and the source system that nobody +has dispositioned. `FiscalCalendarResponse` gains `reconciling_item_count` and +`reconciling_item_sample` (up to five identifiers) so a blocked close names +what is holding it; `ClosePeriodOperation` and `BackfillPlanHistoryOperation` +gain `allow_reconciling_items` to close over them knowingly, which the close +audit note records. The checked-in GraphQL schema gains the matching +`reconcilingItemCount` and `reconcilingItemSample` fields. + +## Also + +- `DeleteSubgraphOp.backup_first` now says what the backup is: a full dump + registered on the parent graph's backup list, downloadable after the + subgraph is gone — and a failed backup aborts the delete. +- `CreateEventBlock`'s description states how a `journal_entry_recorded` + event decides whether to write back to a connected source system, and when + to set `metadata.publish_to_source` to false. + +Nothing is deprecated. diff --git a/robosystems_client/api/extensions_robo_ledger/create_event_block.py b/robosystems_client/api/extensions_robo_ledger/create_event_block.py index 5d8c170..63d031d 100644 --- a/robosystems_client/api/extensions_robo_ledger/create_event_block.py +++ b/robosystems_client/api/extensions_robo_ledger/create_event_block.py @@ -115,7 +115,11 @@ def sync_detailed( Persist a real-world business event. apply_handlers=False (default): capture-only, status='captured'. apply_handlers=True: resolves an event_handler, fires the template, creates GL - entries atomically, status='classified'. Use preview-event-block to dry-run before committing. + entries atomically, status='classified'. Use preview-event-block to dry-run before committing. For + journal_entry_recorded, whether the entry writes back to a connected source system follows `source` + (schedule/manual publish; system does not) unless metadata.publish_to_source says otherwise — set it + false for an alignment entry mirroring a change already made upstream, which would otherwise be + applied twice. **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. @@ -157,7 +161,11 @@ def sync( Persist a real-world business event. apply_handlers=False (default): capture-only, status='captured'. apply_handlers=True: resolves an event_handler, fires the template, creates GL - entries atomically, status='classified'. Use preview-event-block to dry-run before committing. + entries atomically, status='classified'. Use preview-event-block to dry-run before committing. For + journal_entry_recorded, whether the entry writes back to a connected source system follows `source` + (schedule/manual publish; system does not) unless metadata.publish_to_source says otherwise — set it + false for an alignment entry mirroring a change already made upstream, which would otherwise be + applied twice. **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. @@ -194,7 +202,11 @@ async def asyncio_detailed( Persist a real-world business event. apply_handlers=False (default): capture-only, status='captured'. apply_handlers=True: resolves an event_handler, fires the template, creates GL - entries atomically, status='classified'. Use preview-event-block to dry-run before committing. + entries atomically, status='classified'. Use preview-event-block to dry-run before committing. For + journal_entry_recorded, whether the entry writes back to a connected source system follows `source` + (schedule/manual publish; system does not) unless metadata.publish_to_source says otherwise — set it + false for an alignment entry mirroring a change already made upstream, which would otherwise be + applied twice. **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. @@ -234,7 +246,11 @@ async def asyncio( Persist a real-world business event. apply_handlers=False (default): capture-only, status='captured'. apply_handlers=True: resolves an event_handler, fires the template, creates GL - entries atomically, status='classified'. Use preview-event-block to dry-run before committing. + entries atomically, status='classified'. Use preview-event-block to dry-run before committing. For + journal_entry_recorded, whether the entry writes back to a connected source system follows `source` + (schedule/manual publish; system does not) unless metadata.publish_to_source says otherwise — set it + false for an alignment entry mirroring a change already made upstream, which would otherwise be + applied twice. **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. diff --git a/robosystems_client/api/extensions_robo_ledger/preview_reconciling_item.py b/robosystems_client/api/extensions_robo_ledger/preview_reconciling_item.py new file mode 100644 index 0000000..891641a --- /dev/null +++ b/robosystems_client/api/extensions_robo_ledger/preview_reconciling_item.py @@ -0,0 +1,274 @@ +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.operation_envelope_reconciling_item_plan import ( + OperationEnvelopeReconcilingItemPlan, +) +from ...models.preview_reconciling_item_request import PreviewReconcilingItemRequest +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + graph_id: str, + *, + body: PreviewReconcilingItemRequest, + 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/preview-reconciling-item".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 | OperationEnvelopeReconcilingItemPlan | None: + if response.status_code == 200: + response_200 = OperationEnvelopeReconcilingItemPlan.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 = ErrorResponse.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 | OperationEnvelopeReconcilingItemPlan]: + 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: PreviewReconcilingItemRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelopeReconcilingItemPlan]: + """Preview Reconciling Item + + Read what changed on a reconciling item — an event whose source-system payload changed after it was + posted (list them with list-event-blocks is_reconciling_item=true). Returns the posted entries + against the accepted payload, the per-account net difference, which disposition applies by default, + and anything blocking the others. Writes nothing. Run this before resolve-reconciling-item and agree + the treatment with the user — restate moves prior months' figures, catch_up does not. + + **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 (PreviewReconcilingItemRequest): Read what changed on a reconciling item, and what + resolving it would do. + + 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 | OperationEnvelopeReconcilingItemPlan] + """ + + 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: PreviewReconcilingItemRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelopeReconcilingItemPlan | None: + """Preview Reconciling Item + + Read what changed on a reconciling item — an event whose source-system payload changed after it was + posted (list them with list-event-blocks is_reconciling_item=true). Returns the posted entries + against the accepted payload, the per-account net difference, which disposition applies by default, + and anything blocking the others. Writes nothing. Run this before resolve-reconciling-item and agree + the treatment with the user — restate moves prior months' figures, catch_up does not. + + **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 (PreviewReconcilingItemRequest): Read what changed on a reconciling item, and what + resolving it would do. + + 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 | OperationEnvelopeReconcilingItemPlan + """ + + 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: PreviewReconcilingItemRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelopeReconcilingItemPlan]: + """Preview Reconciling Item + + Read what changed on a reconciling item — an event whose source-system payload changed after it was + posted (list them with list-event-blocks is_reconciling_item=true). Returns the posted entries + against the accepted payload, the per-account net difference, which disposition applies by default, + and anything blocking the others. Writes nothing. Run this before resolve-reconciling-item and agree + the treatment with the user — restate moves prior months' figures, catch_up does not. + + **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 (PreviewReconcilingItemRequest): Read what changed on a reconciling item, and what + resolving it would do. + + 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 | OperationEnvelopeReconcilingItemPlan] + """ + + 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: PreviewReconcilingItemRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelopeReconcilingItemPlan | None: + """Preview Reconciling Item + + Read what changed on a reconciling item — an event whose source-system payload changed after it was + posted (list them with list-event-blocks is_reconciling_item=true). Returns the posted entries + against the accepted payload, the per-account net difference, which disposition applies by default, + and anything blocking the others. Writes nothing. Run this before resolve-reconciling-item and agree + the treatment with the user — restate moves prior months' figures, catch_up does not. + + **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 (PreviewReconcilingItemRequest): Read what changed on a reconciling item, and what + resolving it would do. + + 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 | OperationEnvelopeReconcilingItemPlan + """ + + 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/resolve_reconciling_item.py b/robosystems_client/api/extensions_robo_ledger/resolve_reconciling_item.py new file mode 100644 index 0000000..a7752ae --- /dev/null +++ b/robosystems_client/api/extensions_robo_ledger/resolve_reconciling_item.py @@ -0,0 +1,288 @@ +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.operation_envelope_resolve_reconciling_item_response import ( + OperationEnvelopeResolveReconcilingItemResponse, +) +from ...models.resolve_reconciling_item_request import ResolveReconcilingItemRequest +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + graph_id: str, + *, + body: ResolveReconcilingItemRequest, + 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/resolve-reconciling-item".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 | OperationEnvelopeResolveReconcilingItemResponse | None: + if response.status_code == 200: + response_200 = OperationEnvelopeResolveReconcilingItemResponse.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 = ErrorResponse.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 | OperationEnvelopeResolveReconcilingItemResponse]: + 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: ResolveReconcilingItemRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelopeResolveReconcilingItemResponse]: + """Resolve Reconciling Item + + Dispose of one reconciling item and clear its flag. Three treatments: 'restate' regenerates the + event's entries from the accepted payload in place (prior months' figures change — right when + nothing external binds them); 'catch_up' leaves history alone and posts the difference as an + alignment entry in an open period, local-only so it cannot travel back to the source system and + apply the change twice; 'acknowledge' records that the difference was handled elsewhere and clears + the flag without touching the ledger (a note is required, and reference_event_id should name the + entry that handled it). Omit disposition to take the default from preview-reconciling-item. Clearing + the flag means the item stays cleared: the event's payload is set to the accepted one, so the next + sync no longer sees a difference. + + **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 (ResolveReconcilingItemRequest): Dispose of one reconciling item and clear its flag. + + 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 | OperationEnvelopeResolveReconcilingItemResponse] + """ + + 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: ResolveReconcilingItemRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelopeResolveReconcilingItemResponse | None: + """Resolve Reconciling Item + + Dispose of one reconciling item and clear its flag. Three treatments: 'restate' regenerates the + event's entries from the accepted payload in place (prior months' figures change — right when + nothing external binds them); 'catch_up' leaves history alone and posts the difference as an + alignment entry in an open period, local-only so it cannot travel back to the source system and + apply the change twice; 'acknowledge' records that the difference was handled elsewhere and clears + the flag without touching the ledger (a note is required, and reference_event_id should name the + entry that handled it). Omit disposition to take the default from preview-reconciling-item. Clearing + the flag means the item stays cleared: the event's payload is set to the accepted one, so the next + sync no longer sees a difference. + + **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 (ResolveReconcilingItemRequest): Dispose of one reconciling item and clear its flag. + + 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 | OperationEnvelopeResolveReconcilingItemResponse + """ + + 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: ResolveReconcilingItemRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelopeResolveReconcilingItemResponse]: + """Resolve Reconciling Item + + Dispose of one reconciling item and clear its flag. Three treatments: 'restate' regenerates the + event's entries from the accepted payload in place (prior months' figures change — right when + nothing external binds them); 'catch_up' leaves history alone and posts the difference as an + alignment entry in an open period, local-only so it cannot travel back to the source system and + apply the change twice; 'acknowledge' records that the difference was handled elsewhere and clears + the flag without touching the ledger (a note is required, and reference_event_id should name the + entry that handled it). Omit disposition to take the default from preview-reconciling-item. Clearing + the flag means the item stays cleared: the event's payload is set to the accepted one, so the next + sync no longer sees a difference. + + **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 (ResolveReconcilingItemRequest): Dispose of one reconciling item and clear its flag. + + 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 | OperationEnvelopeResolveReconcilingItemResponse] + """ + + 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: ResolveReconcilingItemRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelopeResolveReconcilingItemResponse | None: + """Resolve Reconciling Item + + Dispose of one reconciling item and clear its flag. Three treatments: 'restate' regenerates the + event's entries from the accepted payload in place (prior months' figures change — right when + nothing external binds them); 'catch_up' leaves history alone and posts the difference as an + alignment entry in an open period, local-only so it cannot travel back to the source system and + apply the change twice; 'acknowledge' records that the difference was handled elsewhere and clears + the flag without touching the ledger (a note is required, and reference_event_id should name the + entry that handled it). Omit disposition to take the default from preview-reconciling-item. Clearing + the flag means the item stays cleared: the event's payload is set to the accepted one, so the next + sync no longer sees a difference. + + **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 (ResolveReconcilingItemRequest): Dispose of one reconciling item and clear its flag. + + 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 | OperationEnvelopeResolveReconcilingItemResponse + """ + + return ( + await asyncio_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ) + ).parsed diff --git a/robosystems_client/api/mcp/__init__.py b/robosystems_client/api/mcp/__init__.py deleted file mode 100644 index 2d7c0b2..0000000 --- a/robosystems_client/api/mcp/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Contains endpoint functions for accessing the API""" diff --git a/robosystems_client/api/mcp/call_mcp_tool.py b/robosystems_client/api/mcp/call_mcp_tool.py deleted file mode 100644 index 69b7a5e..0000000 --- a/robosystems_client/api/mcp/call_mcp_tool.py +++ /dev/null @@ -1,279 +0,0 @@ -from http import HTTPStatus -from typing import Any, cast -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.mcp_tool_call import MCPToolCall -from ...types import UNSET, Response, Unset - - -def _get_kwargs( - graph_id: str, - *, - body: MCPToolCall, - format_: None | str | Unset = UNSET, - test_mode: bool | Unset = False, -) -> dict[str, Any]: - headers: dict[str, Any] = {} - - params: dict[str, Any] = {} - - json_format_: None | str | Unset - if isinstance(format_, Unset): - json_format_ = UNSET - else: - json_format_ = format_ - params["format"] = json_format_ - - params["test_mode"] = test_mode - - params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - - _kwargs: dict[str, Any] = { - "method": "post", - "url": "/v1/graphs/{graph_id}/mcp/call-tool".format( - graph_id=quote(str(graph_id), safe=""), - ), - "params": params, - } - - _kwargs["json"] = body.to_dict() - - headers["Content-Type"] = "application/json" - - _kwargs["headers"] = headers - return _kwargs - - -def _parse_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> Any | ErrorResponse | HTTPValidationError | None: - if response.status_code == 200: - response_200 = response.json() - return response_200 - - if response.status_code == 202: - response_202 = cast(Any, None) - return response_202 - - 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 == 408: - response_408 = cast(Any, None) - return response_408 - - 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 response.status_code == 503: - response_503 = cast(Any, None) - return response_503 - - 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[Any | ErrorResponse | HTTPValidationError]: - 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: MCPToolCall, - format_: None | str | Unset = UNSET, - test_mode: bool | Unset = False, -) -> Response[Any | ErrorResponse | HTTPValidationError]: - """Execute MCP Tool - - Strategy auto-selected by tool type and load: JSON for fast tools, SSE for long queries, NDJSON for - large results. Database operations (Cypher, schema, info) consume no credits — only AI LLM calls - cost credits. - - Args: - graph_id (str): - format_ (None | str | Unset): Response format override (json, sse, ndjson) - test_mode (bool | Unset): Enable test mode for debugging Default: False. - body (MCPToolCall): Request model for MCP tool execution. - - 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[Any | ErrorResponse | HTTPValidationError] - """ - - kwargs = _get_kwargs( - graph_id=graph_id, - body=body, - format_=format_, - test_mode=test_mode, - ) - - response = client.get_httpx_client().request( - **kwargs, - ) - - return _build_response(client=client, response=response) - - -def sync( - graph_id: str, - *, - client: AuthenticatedClient, - body: MCPToolCall, - format_: None | str | Unset = UNSET, - test_mode: bool | Unset = False, -) -> Any | ErrorResponse | HTTPValidationError | None: - """Execute MCP Tool - - Strategy auto-selected by tool type and load: JSON for fast tools, SSE for long queries, NDJSON for - large results. Database operations (Cypher, schema, info) consume no credits — only AI LLM calls - cost credits. - - Args: - graph_id (str): - format_ (None | str | Unset): Response format override (json, sse, ndjson) - test_mode (bool | Unset): Enable test mode for debugging Default: False. - body (MCPToolCall): Request model for MCP tool execution. - - 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: - Any | ErrorResponse | HTTPValidationError - """ - - return sync_detailed( - graph_id=graph_id, - client=client, - body=body, - format_=format_, - test_mode=test_mode, - ).parsed - - -async def asyncio_detailed( - graph_id: str, - *, - client: AuthenticatedClient, - body: MCPToolCall, - format_: None | str | Unset = UNSET, - test_mode: bool | Unset = False, -) -> Response[Any | ErrorResponse | HTTPValidationError]: - """Execute MCP Tool - - Strategy auto-selected by tool type and load: JSON for fast tools, SSE for long queries, NDJSON for - large results. Database operations (Cypher, schema, info) consume no credits — only AI LLM calls - cost credits. - - Args: - graph_id (str): - format_ (None | str | Unset): Response format override (json, sse, ndjson) - test_mode (bool | Unset): Enable test mode for debugging Default: False. - body (MCPToolCall): Request model for MCP tool execution. - - 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[Any | ErrorResponse | HTTPValidationError] - """ - - kwargs = _get_kwargs( - graph_id=graph_id, - body=body, - format_=format_, - test_mode=test_mode, - ) - - 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: MCPToolCall, - format_: None | str | Unset = UNSET, - test_mode: bool | Unset = False, -) -> Any | ErrorResponse | HTTPValidationError | None: - """Execute MCP Tool - - Strategy auto-selected by tool type and load: JSON for fast tools, SSE for long queries, NDJSON for - large results. Database operations (Cypher, schema, info) consume no credits — only AI LLM calls - cost credits. - - Args: - graph_id (str): - format_ (None | str | Unset): Response format override (json, sse, ndjson) - test_mode (bool | Unset): Enable test mode for debugging Default: False. - body (MCPToolCall): Request model for MCP tool execution. - - 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: - Any | ErrorResponse | HTTPValidationError - """ - - return ( - await asyncio_detailed( - graph_id=graph_id, - client=client, - body=body, - format_=format_, - test_mode=test_mode, - ) - ).parsed diff --git a/robosystems_client/api/user/list_user_o_auth_grants.py b/robosystems_client/api/user/list_user_o_auth_grants.py new file mode 100644 index 0000000..2055058 --- /dev/null +++ b/robosystems_client/api/user/list_user_o_auth_grants.py @@ -0,0 +1,166 @@ +from http import HTTPStatus +from typing import Any + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.error_response import ErrorResponse +from ...models.o_auth_grants_response import OAuthGrantsResponse +from ...types import Response + + +def _get_kwargs() -> dict[str, Any]: + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/v1/user/oauth/grants", + } + + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> ErrorResponse | OAuthGrantsResponse | None: + if response.status_code == 200: + response_200 = OAuthGrantsResponse.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 == 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 | OAuthGrantsResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient, +) -> Response[ErrorResponse | OAuthGrantsResponse]: + """List Connected Apps + + Every MCP client the user has authorized through OAuth, with the one graph each connection reaches. + Active grants only: a revoked grant cannot be reinstated, so it leaves the list. + + 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 | OAuthGrantsResponse] + """ + + kwargs = _get_kwargs() + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient, +) -> ErrorResponse | OAuthGrantsResponse | None: + """List Connected Apps + + Every MCP client the user has authorized through OAuth, with the one graph each connection reaches. + Active grants only: a revoked grant cannot be reinstated, so it leaves the list. + + 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 | OAuthGrantsResponse + """ + + return sync_detailed( + client=client, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient, +) -> Response[ErrorResponse | OAuthGrantsResponse]: + """List Connected Apps + + Every MCP client the user has authorized through OAuth, with the one graph each connection reaches. + Active grants only: a revoked grant cannot be reinstated, so it leaves the list. + + 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 | OAuthGrantsResponse] + """ + + kwargs = _get_kwargs() + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient, +) -> ErrorResponse | OAuthGrantsResponse | None: + """List Connected Apps + + Every MCP client the user has authorized through OAuth, with the one graph each connection reaches. + Active grants only: a revoked grant cannot be reinstated, so it leaves the list. + + 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 | OAuthGrantsResponse + """ + + return ( + await asyncio_detailed( + client=client, + ) + ).parsed diff --git a/robosystems_client/api/mcp/list_mcp_tools.py b/robosystems_client/api/user/revoke_user_o_auth_grant.py similarity index 61% rename from robosystems_client/api/mcp/list_mcp_tools.py rename to robosystems_client/api/user/revoke_user_o_auth_grant.py index a54e992..2847e42 100644 --- a/robosystems_client/api/mcp/list_mcp_tools.py +++ b/robosystems_client/api/user/revoke_user_o_auth_grant.py @@ -8,18 +8,18 @@ from ...client import AuthenticatedClient, Client from ...models.error_response import ErrorResponse from ...models.http_validation_error import HTTPValidationError -from ...models.mcp_tools_response import MCPToolsResponse +from ...models.success_response import SuccessResponse from ...types import Response def _get_kwargs( - graph_id: str, + grant_id: str, ) -> dict[str, Any]: _kwargs: dict[str, Any] = { - "method": "get", - "url": "/v1/graphs/{graph_id}/mcp/tools".format( - graph_id=quote(str(graph_id), safe=""), + "method": "delete", + "url": "/v1/user/oauth/grants/{grant_id}".format( + grant_id=quote(str(grant_id), safe=""), ), } @@ -28,9 +28,9 @@ def _get_kwargs( def _parse_response( *, client: AuthenticatedClient | Client, response: httpx.Response -) -> ErrorResponse | HTTPValidationError | MCPToolsResponse | None: +) -> ErrorResponse | HTTPValidationError | SuccessResponse | None: if response.status_code == 200: - response_200 = MCPToolsResponse.from_dict(response.json()) + response_200 = SuccessResponse.from_dict(response.json()) return response_200 @@ -77,7 +77,7 @@ def _parse_response( def _build_response( *, client: AuthenticatedClient | Client, response: httpx.Response -) -> Response[ErrorResponse | HTTPValidationError | MCPToolsResponse]: +) -> Response[ErrorResponse | HTTPValidationError | SuccessResponse]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -87,28 +87,29 @@ def _build_response( def sync_detailed( - graph_id: str, + grant_id: str, *, client: AuthenticatedClient, -) -> Response[ErrorResponse | HTTPValidationError | MCPToolsResponse]: - """List MCP Tools +) -> Response[ErrorResponse | HTTPValidationError | SuccessResponse]: + """Revoke Connected App - Returns tool schemas with capability hints (streaming, caching, timeouts) per tool. Tool list is - context-aware by graph type; identical for parent graphs and subgraphs. + Revokes the grant and every access and refresh token minted from it. The client's next request fails + with 401 and it must ask the user to authorize again. Revoking an already revoked grant succeeds and + changes nothing. Args: - graph_id (str): + grant_id (str): 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 | MCPToolsResponse] + Response[ErrorResponse | HTTPValidationError | SuccessResponse] """ kwargs = _get_kwargs( - graph_id=graph_id, + grant_id=grant_id, ) response = client.get_httpx_client().request( @@ -119,55 +120,57 @@ def sync_detailed( def sync( - graph_id: str, + grant_id: str, *, client: AuthenticatedClient, -) -> ErrorResponse | HTTPValidationError | MCPToolsResponse | None: - """List MCP Tools +) -> ErrorResponse | HTTPValidationError | SuccessResponse | None: + """Revoke Connected App - Returns tool schemas with capability hints (streaming, caching, timeouts) per tool. Tool list is - context-aware by graph type; identical for parent graphs and subgraphs. + Revokes the grant and every access and refresh token minted from it. The client's next request fails + with 401 and it must ask the user to authorize again. Revoking an already revoked grant succeeds and + changes nothing. Args: - graph_id (str): + grant_id (str): 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 | MCPToolsResponse + ErrorResponse | HTTPValidationError | SuccessResponse """ return sync_detailed( - graph_id=graph_id, + grant_id=grant_id, client=client, ).parsed async def asyncio_detailed( - graph_id: str, + grant_id: str, *, client: AuthenticatedClient, -) -> Response[ErrorResponse | HTTPValidationError | MCPToolsResponse]: - """List MCP Tools +) -> Response[ErrorResponse | HTTPValidationError | SuccessResponse]: + """Revoke Connected App - Returns tool schemas with capability hints (streaming, caching, timeouts) per tool. Tool list is - context-aware by graph type; identical for parent graphs and subgraphs. + Revokes the grant and every access and refresh token minted from it. The client's next request fails + with 401 and it must ask the user to authorize again. Revoking an already revoked grant succeeds and + changes nothing. Args: - graph_id (str): + grant_id (str): 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 | MCPToolsResponse] + Response[ErrorResponse | HTTPValidationError | SuccessResponse] """ kwargs = _get_kwargs( - graph_id=graph_id, + grant_id=grant_id, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -176,29 +179,30 @@ async def asyncio_detailed( async def asyncio( - graph_id: str, + grant_id: str, *, client: AuthenticatedClient, -) -> ErrorResponse | HTTPValidationError | MCPToolsResponse | None: - """List MCP Tools +) -> ErrorResponse | HTTPValidationError | SuccessResponse | None: + """Revoke Connected App - Returns tool schemas with capability hints (streaming, caching, timeouts) per tool. Tool list is - context-aware by graph type; identical for parent graphs and subgraphs. + Revokes the grant and every access and refresh token minted from it. The client's next request fails + with 401 and it must ask the user to authorize again. Revoking an already revoked grant succeeds and + changes nothing. Args: - graph_id (str): + grant_id (str): 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 | MCPToolsResponse + ErrorResponse | HTTPValidationError | SuccessResponse """ return ( await asyncio_detailed( - graph_id=graph_id, + grant_id=grant_id, client=client, ) ).parsed diff --git a/robosystems_client/graphql/schema.graphql b/robosystems_client/graphql/schema.graphql index 9100733..013a44b 100644 --- a/robosystems_client/graphql/schema.graphql +++ b/robosystems_client/graphql/schema.graphql @@ -1215,6 +1215,16 @@ type FiscalCalendar { Sample of up to 5 stranded obligations (schedule_id, schedule_name, period, event_id) ordered by occurred_at. """ strandedObligationSample: [PendingObligationDetail!]! + + """ + Posted events in or before this period whose source payload changed afterwards and that nobody has dispositioned — differences between the books and the source system. Resolve each with resolve-reconciling-item, or close over them knowingly with allow_reconciling_items. + """ + reconcilingItemCount: Int! + + """ + Source identifiers (or event ids) of up to 5 unresolved reconciling items, so the blocker names what is holding the close. + """ + reconcilingItemSample: [String!]! lastCloseAt: DateTime initializedAt: DateTime diff --git a/robosystems_client/models/__init__.py b/robosystems_client/models/__init__.py index 22fb379..49ae413 100644 --- a/robosystems_client/models/__init__.py +++ b/robosystems_client/models/__init__.py @@ -343,10 +343,6 @@ from .login_request import LoginRequest from .logout_user_response_logoutuser import LogoutUserResponseLogoutuser from .materialize_op import MaterializeOp -from .mcp_tool_call import MCPToolCall -from .mcp_tool_call_arguments import MCPToolCallArguments -from .mcp_tools_response import MCPToolsResponse -from .mcp_tools_response_tools_item import MCPToolsResponseToolsItem from .memory_list_response import MemoryListResponse from .memory_recall_request import MemoryRecallRequest from .memory_record import MemoryRecord @@ -359,6 +355,8 @@ from .mfa_verify_request_assertion_type_0 import MfaVerifyRequestAssertionType0 from .o_auth_callback_request import OAuthCallbackRequest from .o_auth_callback_response import OAuthCallbackResponse +from .o_auth_grant_info import OAuthGrantInfo +from .o_auth_grants_response import OAuthGrantsResponse from .o_auth_init_request import OAuthInitRequest from .o_auth_init_request_additional_params_type_0 import ( OAuthInitRequestAdditionalParamsType0, @@ -559,10 +557,22 @@ from .operation_envelope_publish_list_response_status import ( OperationEnvelopePublishListResponseStatus, ) +from .operation_envelope_reconciling_item_plan import ( + OperationEnvelopeReconcilingItemPlan, +) +from .operation_envelope_reconciling_item_plan_status import ( + OperationEnvelopeReconcilingItemPlanStatus, +) from .operation_envelope_report_response import OperationEnvelopeReportResponse from .operation_envelope_report_response_status import ( OperationEnvelopeReportResponseStatus, ) +from .operation_envelope_resolve_reconciling_item_response import ( + OperationEnvelopeResolveReconcilingItemResponse, +) +from .operation_envelope_resolve_reconciling_item_response_status import ( + OperationEnvelopeResolveReconcilingItemResponseStatus, +) from .operation_envelope_revoke_report_share_response import ( OperationEnvelopeRevokeReportShareResponse, ) @@ -679,6 +689,7 @@ from .preview_event_block_response_handler_metadata import ( PreviewEventBlockResponseHandlerMetadata, ) +from .preview_reconciling_item_request import PreviewReconcilingItemRequest from .promote_obligations_request import PromoteObligationsRequest from .promote_obligations_response import PromoteObligationsResponse from .promote_obligations_response_errors_item import ( @@ -690,6 +701,14 @@ from .quick_books_connection_config import QuickBooksConnectionConfig from .rate_limits import RateLimits from .rebuild_schedule_request import RebuildScheduleRequest +from .reconciling_item_catch_up import ReconcilingItemCatchUp +from .reconciling_item_delta_line import ReconcilingItemDeltaLine +from .reconciling_item_entry_summary import ReconcilingItemEntrySummary +from .reconciling_item_plan import ReconcilingItemPlan +from .reconciling_item_plan_default_disposition import ( + ReconcilingItemPlanDefaultDisposition, +) +from .reconciling_item_regenerated import ReconcilingItemRegenerated from .recovery_codes_request import RecoveryCodesRequest from .recovery_codes_request_assertion_type_0 import RecoveryCodesRequestAssertionType0 from .recovery_codes_response import RecoveryCodesResponse @@ -711,6 +730,15 @@ ) from .reset_password_request import ResetPasswordRequest from .reset_password_validate_response import ResetPasswordValidateResponse +from .resolve_reconciling_item_request import ResolveReconcilingItemRequest +from .resolve_reconciling_item_request_disposition_type_0 import ( + ResolveReconcilingItemRequestDispositionType0, +) +from .resolve_reconciling_item_request_status import ResolveReconcilingItemRequestStatus +from .resolve_reconciling_item_response import ResolveReconcilingItemResponse +from .resolve_reconciling_item_response_disposition import ( + ResolveReconcilingItemResponseDisposition, +) from .resolved_report_info import ResolvedReportInfo from .response_mode import ResponseMode from .revoke_report_share_operation import RevokeReportShareOperation @@ -1204,10 +1232,6 @@ "LoginRequest", "LogoutUserResponseLogoutuser", "MaterializeOp", - "MCPToolCall", - "MCPToolCallArguments", - "MCPToolsResponse", - "MCPToolsResponseToolsItem", "MemoryListResponse", "MemoryRecallRequest", "MemoryRecord", @@ -1220,6 +1244,8 @@ "MfaVerifyRequestAssertionType0", "OAuthCallbackRequest", "OAuthCallbackResponse", + "OAuthGrantInfo", + "OAuthGrantsResponse", "OAuthInitRequest", "OAuthInitRequestAdditionalParamsType0", "OAuthInitResponse", @@ -1296,8 +1322,12 @@ "OperationEnvelopePromoteObligationsResponseStatus", "OperationEnvelopePublishListResponse", "OperationEnvelopePublishListResponseStatus", + "OperationEnvelopeReconcilingItemPlan", + "OperationEnvelopeReconcilingItemPlanStatus", "OperationEnvelopeReportResponse", "OperationEnvelopeReportResponseStatus", + "OperationEnvelopeResolveReconcilingItemResponse", + "OperationEnvelopeResolveReconcilingItemResponseStatus", "OperationEnvelopeRevokeReportShareResponse", "OperationEnvelopeRevokeReportShareResponseStatus", "OperationEnvelopeScheduleCreatedResponse", @@ -1378,6 +1408,7 @@ "PositionBlock", "PreviewEventBlockResponse", "PreviewEventBlockResponseHandlerMetadata", + "PreviewReconcilingItemRequest", "PromoteObligationsRequest", "PromoteObligationsResponse", "PromoteObligationsResponseErrorsItem", @@ -1387,6 +1418,12 @@ "QuickBooksConnectionConfig", "RateLimits", "RebuildScheduleRequest", + "ReconcilingItemCatchUp", + "ReconcilingItemDeltaLine", + "ReconcilingItemEntrySummary", + "ReconcilingItemPlan", + "ReconcilingItemPlanDefaultDisposition", + "ReconcilingItemRegenerated", "RecoveryCodesRequest", "RecoveryCodesRequestAssertionType0", "RecoveryCodesResponse", @@ -1407,6 +1444,11 @@ "ResetPasswordRequest", "ResetPasswordValidateResponse", "ResolvedReportInfo", + "ResolveReconcilingItemRequest", + "ResolveReconcilingItemRequestDispositionType0", + "ResolveReconcilingItemRequestStatus", + "ResolveReconcilingItemResponse", + "ResolveReconcilingItemResponseDisposition", "ResponseMode", "RevokeReportShareOperation", "RevokeReportShareResponse", diff --git a/robosystems_client/models/backfill_plan_history_operation.py b/robosystems_client/models/backfill_plan_history_operation.py index acb2fa5..695c78e 100644 --- a/robosystems_client/models/backfill_plan_history_operation.py +++ b/robosystems_client/models/backfill_plan_history_operation.py @@ -25,6 +25,9 @@ class BackfillPlanHistoryOperation: allow_stranded_obligations (bool | Unset): Override the stranded-obligation gate on each reclose. Only needed when a matured classified obligation without a drafted entry exists inside the backfill window and you have decided not to draft or void it first. Default: False. + allow_reconciling_items (bool | Unset): Override the reconciling-item gate on each reclose. Only needed when an + event inside the backfill window is still flagged as changed upstream and you have decided not to resolve it + first. Default: False. restamp (bool | Unset): Also re-derive months that ALREADY have canonical statement sets (default: skip them). Use after an engine improvement changes what a stamp produces — each month reruns the full reopen → reclose cycle and replaces its sets. A restamp run is not self-resuming (every month in range stays a candidate); @@ -36,6 +39,7 @@ class BackfillPlanHistoryOperation: max_periods: int | Unset = 12 allow_stale_sync: bool | Unset = False allow_stranded_obligations: bool | Unset = False + allow_reconciling_items: bool | Unset = False restamp: bool | Unset = False note: None | str | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -53,6 +57,8 @@ def to_dict(self) -> dict[str, Any]: allow_stranded_obligations = self.allow_stranded_obligations + allow_reconciling_items = self.allow_reconciling_items + restamp = self.restamp note: None | str | Unset @@ -72,6 +78,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["allow_stale_sync"] = allow_stale_sync if allow_stranded_obligations is not UNSET: field_dict["allow_stranded_obligations"] = allow_stranded_obligations + if allow_reconciling_items is not UNSET: + field_dict["allow_reconciling_items"] = allow_reconciling_items if restamp is not UNSET: field_dict["restamp"] = restamp if note is not UNSET: @@ -98,6 +106,8 @@ def _parse_start_period(data: object) -> None | str | Unset: allow_stranded_obligations = d.pop("allow_stranded_obligations", UNSET) + allow_reconciling_items = d.pop("allow_reconciling_items", UNSET) + restamp = d.pop("restamp", UNSET) def _parse_note(data: object) -> None | str | Unset: @@ -114,6 +124,7 @@ def _parse_note(data: object) -> None | str | Unset: max_periods=max_periods, allow_stale_sync=allow_stale_sync, allow_stranded_obligations=allow_stranded_obligations, + allow_reconciling_items=allow_reconciling_items, restamp=restamp, note=note, ) diff --git a/robosystems_client/models/close_period_operation.py b/robosystems_client/models/close_period_operation.py index e4d267d..8ef4ebc 100644 --- a/robosystems_client/models/close_period_operation.py +++ b/robosystems_client/models/close_period_operation.py @@ -26,12 +26,17 @@ class ClosePeriodOperation: classified obligations have no drafted closing entry, knowingly omitting those adjusting entries from the period. Prefer running promote-obligations with dispatch_handlers=true (which drafts them) or voiding the obligations instead. The override is recorded in the close audit note. Default: False. + allow_reconciling_items (bool | Unset): Override the reconciling-item gate — close even though posted events in + the period are still flagged as changed in the source system, leaving those differences undecided. The next sync + will still report them, and the statements stamped by this close may disagree with the source. Prefer resolve- + reconciling-item on each first. The override is recorded in the close audit note. Default: False. """ period: str note: None | str | Unset = UNSET allow_stale_sync: bool | Unset = False allow_stranded_obligations: bool | Unset = False + allow_reconciling_items: bool | Unset = False additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -47,6 +52,8 @@ def to_dict(self) -> dict[str, Any]: allow_stranded_obligations = self.allow_stranded_obligations + allow_reconciling_items = self.allow_reconciling_items + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -60,6 +67,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["allow_stale_sync"] = allow_stale_sync if allow_stranded_obligations is not UNSET: field_dict["allow_stranded_obligations"] = allow_stranded_obligations + if allow_reconciling_items is not UNSET: + field_dict["allow_reconciling_items"] = allow_reconciling_items return field_dict @@ -81,11 +90,14 @@ def _parse_note(data: object) -> None | str | Unset: allow_stranded_obligations = d.pop("allow_stranded_obligations", UNSET) + allow_reconciling_items = d.pop("allow_reconciling_items", UNSET) + close_period_operation = cls( period=period, note=note, allow_stale_sync=allow_stale_sync, allow_stranded_obligations=allow_stranded_obligations, + allow_reconciling_items=allow_reconciling_items, ) close_period_operation.additional_properties = d diff --git a/robosystems_client/models/delete_subgraph_op.py b/robosystems_client/models/delete_subgraph_op.py index e27de6e..259a64c 100644 --- a/robosystems_client/models/delete_subgraph_op.py +++ b/robosystems_client/models/delete_subgraph_op.py @@ -18,7 +18,9 @@ class DeleteSubgraphOp: Attributes: subgraph_name (str): Subgraph name to delete (e.g., 'dev', 'staging') force (bool | Unset): Delete even if subgraph contains data Default: False. - backup_first (bool | Unset): Create a backup before deleting Default: True. + backup_first (bool | Unset): Take a full backup of the subgraph before deleting it. The backup is registered on + the parent graph's backup list, where it can be listed and downloaded after the subgraph is gone. If the backup + fails the subgraph is not deleted. Default: True. """ subgraph_name: str diff --git a/robosystems_client/models/fiscal_calendar_response.py b/robosystems_client/models/fiscal_calendar_response.py index b1dbd9d..a797c1a 100644 --- a/robosystems_client/models/fiscal_calendar_response.py +++ b/robosystems_client/models/fiscal_calendar_response.py @@ -52,6 +52,11 @@ class FiscalCalendarResponse: 0. stranded_obligation_sample (list[PendingObligationDetailResponse] | Unset): Sample of up to 5 stranded obligations (schedule_id, schedule_name, period, event_id) ordered by occurred_at. + reconciling_item_count (int | Unset): Posted events in or before this period whose source payload changed + afterwards and that nobody has dispositioned — differences between the books and the source system. Resolve each + with resolve-reconciling-item, or close over them knowingly with allow_reconciling_items. Default: 0. + reconciling_item_sample (list[str] | Unset): Source identifiers (or event ids) of up to 5 unresolved reconciling + items, so the blocker names what is holding the close. last_close_at (datetime.datetime | None | Unset): initialized_at (datetime.datetime | None | Unset): last_sync_at (datetime.datetime | None | Unset): Most recent QB sync timestamp (if connected) @@ -72,6 +77,8 @@ class FiscalCalendarResponse: sync_stale_days: int | None | Unset = UNSET stranded_obligation_count: int | Unset = 0 stranded_obligation_sample: list[PendingObligationDetailResponse] | Unset = UNSET + reconciling_item_count: int | Unset = 0 + reconciling_item_sample: list[str] | Unset = UNSET last_close_at: datetime.datetime | None | Unset = UNSET initialized_at: datetime.datetime | None | Unset = UNSET last_sync_at: datetime.datetime | None | Unset = UNSET @@ -137,6 +144,12 @@ def to_dict(self) -> dict[str, Any]: stranded_obligation_sample_item = stranded_obligation_sample_item_data.to_dict() stranded_obligation_sample.append(stranded_obligation_sample_item) + reconciling_item_count = self.reconciling_item_count + + reconciling_item_sample: list[str] | Unset = UNSET + if not isinstance(self.reconciling_item_sample, Unset): + reconciling_item_sample = self.reconciling_item_sample + last_close_at: None | str | Unset if isinstance(self.last_close_at, Unset): last_close_at = UNSET @@ -200,6 +213,10 @@ def to_dict(self) -> dict[str, Any]: field_dict["stranded_obligation_count"] = stranded_obligation_count if stranded_obligation_sample is not UNSET: field_dict["stranded_obligation_sample"] = stranded_obligation_sample + if reconciling_item_count is not UNSET: + field_dict["reconciling_item_count"] = reconciling_item_count + if reconciling_item_sample is not UNSET: + field_dict["reconciling_item_sample"] = reconciling_item_sample if last_close_at is not UNSET: field_dict["last_close_at"] = last_close_at if initialized_at is not UNSET: @@ -295,6 +312,10 @@ def _parse_sync_stale_days(data: object) -> int | None | Unset: stranded_obligation_sample.append(stranded_obligation_sample_item) + reconciling_item_count = d.pop("reconciling_item_count", UNSET) + + reconciling_item_sample = cast(list[str], d.pop("reconciling_item_sample", UNSET)) + def _parse_last_close_at(data: object) -> datetime.datetime | None | Unset: if data is None: return data @@ -370,6 +391,8 @@ def _parse_last_sync_at(data: object) -> datetime.datetime | None | Unset: sync_stale_days=sync_stale_days, stranded_obligation_count=stranded_obligation_count, stranded_obligation_sample=stranded_obligation_sample, + reconciling_item_count=reconciling_item_count, + reconciling_item_sample=reconciling_item_sample, last_close_at=last_close_at, initialized_at=initialized_at, last_sync_at=last_sync_at, diff --git a/robosystems_client/models/mcp_tools_response.py b/robosystems_client/models/mcp_tools_response.py deleted file mode 100644 index 7d255c9..0000000 --- a/robosystems_client/models/mcp_tools_response.py +++ /dev/null @@ -1,100 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar, cast - -from attrs import define as _attrs_define -from attrs import field as _attrs_field - -from ..types import UNSET, Unset - -if TYPE_CHECKING: - from ..models.mcp_tools_response_tools_item import MCPToolsResponseToolsItem - - -T = TypeVar("T", bound="MCPToolsResponse") - - -@_attrs_define -class MCPToolsResponse: - """Response model for MCP tools listing. - - Attributes: - tools (list[MCPToolsResponseToolsItem]): List of available MCP tools with their schemas - instructions (None | str | Unset): Per-graph routing guidance for MCP clients, tailored to the graph's category - and live tool set. Clients should pass this to the MCP server's `instructions` handshake field so it is always - in the agent's context. - """ - - tools: list[MCPToolsResponseToolsItem] - instructions: None | str | Unset = UNSET - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - tools = [] - for tools_item_data in self.tools: - tools_item = tools_item_data.to_dict() - tools.append(tools_item) - - instructions: None | str | Unset - if isinstance(self.instructions, Unset): - instructions = UNSET - else: - instructions = self.instructions - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update( - { - "tools": tools, - } - ) - if instructions is not UNSET: - field_dict["instructions"] = instructions - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.mcp_tools_response_tools_item import MCPToolsResponseToolsItem - - d = dict(src_dict) - tools = [] - _tools = d.pop("tools") - for tools_item_data in _tools: - tools_item = MCPToolsResponseToolsItem.from_dict(tools_item_data) - - tools.append(tools_item) - - def _parse_instructions(data: object) -> None | str | Unset: - if data is None: - return data - if isinstance(data, Unset): - return data - return cast(None | str | Unset, data) - - instructions = _parse_instructions(d.pop("instructions", UNSET)) - - mcp_tools_response = cls( - tools=tools, - instructions=instructions, - ) - - mcp_tools_response.additional_properties = d - return mcp_tools_response - - @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/mcp_tools_response_tools_item.py b/robosystems_client/models/mcp_tools_response_tools_item.py deleted file mode 100644 index b17c95d..0000000 --- a/robosystems_client/models/mcp_tools_response_tools_item.py +++ /dev/null @@ -1,47 +0,0 @@ -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="MCPToolsResponseToolsItem") - - -@_attrs_define -class MCPToolsResponseToolsItem: - """ """ - - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - mcp_tools_response_tools_item = cls() - - mcp_tools_response_tools_item.additional_properties = d - return mcp_tools_response_tools_item - - @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/o_auth_grant_info.py b/robosystems_client/models/o_auth_grant_info.py new file mode 100644 index 0000000..f14df52 --- /dev/null +++ b/robosystems_client/models/o_auth_grant_info.py @@ -0,0 +1,173 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="OAuthGrantInfo") + + +@_attrs_define +class OAuthGrantInfo: + """A connected app: one OAuth consent for one client on one graph. + + Attributes: + id (str): Grant ID + client_name (str): The connected client's display name + client_is_trusted (bool): Whether the client is on the trusted list (pre-registered, or a known metadata- + document host). Untrusted clients registered themselves. + graph_id (str): The one graph this consent reaches + resource (str): The MCP URL the grant's tokens are bound to (their audience) + scope (str): Granted scopes, space-separated + created_at (str): When the user consented + client_uri (None | str | Unset): The client's homepage, if declared + graph_name (None | str | Unset): The graph's display name, when the graph still exists + last_used_at (None | str | Unset): Last token use, if any + """ + + id: str + client_name: str + client_is_trusted: bool + graph_id: str + resource: str + scope: str + created_at: str + client_uri: None | str | Unset = UNSET + graph_name: None | str | Unset = UNSET + last_used_at: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + id = self.id + + client_name = self.client_name + + client_is_trusted = self.client_is_trusted + + graph_id = self.graph_id + + resource = self.resource + + scope = self.scope + + created_at = self.created_at + + client_uri: None | str | Unset + if isinstance(self.client_uri, Unset): + client_uri = UNSET + else: + client_uri = self.client_uri + + graph_name: None | str | Unset + if isinstance(self.graph_name, Unset): + graph_name = UNSET + else: + graph_name = self.graph_name + + last_used_at: None | str | Unset + if isinstance(self.last_used_at, Unset): + last_used_at = UNSET + else: + last_used_at = self.last_used_at + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "id": id, + "client_name": client_name, + "client_is_trusted": client_is_trusted, + "graph_id": graph_id, + "resource": resource, + "scope": scope, + "created_at": created_at, + } + ) + if client_uri is not UNSET: + field_dict["client_uri"] = client_uri + if graph_name is not UNSET: + field_dict["graph_name"] = graph_name + if last_used_at is not UNSET: + field_dict["last_used_at"] = last_used_at + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + id = d.pop("id") + + client_name = d.pop("client_name") + + client_is_trusted = d.pop("client_is_trusted") + + graph_id = d.pop("graph_id") + + resource = d.pop("resource") + + scope = d.pop("scope") + + created_at = d.pop("created_at") + + def _parse_client_uri(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + client_uri = _parse_client_uri(d.pop("client_uri", UNSET)) + + def _parse_graph_name(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + graph_name = _parse_graph_name(d.pop("graph_name", UNSET)) + + def _parse_last_used_at(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + last_used_at = _parse_last_used_at(d.pop("last_used_at", UNSET)) + + o_auth_grant_info = cls( + id=id, + client_name=client_name, + client_is_trusted=client_is_trusted, + graph_id=graph_id, + resource=resource, + scope=scope, + created_at=created_at, + client_uri=client_uri, + graph_name=graph_name, + last_used_at=last_used_at, + ) + + o_auth_grant_info.additional_properties = d + return o_auth_grant_info + + @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/mcp_tool_call.py b/robosystems_client/models/o_auth_grants_response.py similarity index 50% rename from robosystems_client/models/mcp_tool_call.py rename to robosystems_client/models/o_auth_grants_response.py index dea9860..c567910 100644 --- a/robosystems_client/models/mcp_tool_call.py +++ b/robosystems_client/models/o_auth_grants_response.py @@ -6,68 +6,58 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field -from ..types import UNSET, Unset - if TYPE_CHECKING: - from ..models.mcp_tool_call_arguments import MCPToolCallArguments + from ..models.o_auth_grant_info import OAuthGrantInfo -T = TypeVar("T", bound="MCPToolCall") +T = TypeVar("T", bound="OAuthGrantsResponse") @_attrs_define -class MCPToolCall: - """Request model for MCP tool execution. +class OAuthGrantsResponse: + """Response model for listing connected apps. Attributes: - name (str): Name of the MCP tool to execute - arguments (MCPToolCallArguments | Unset): Arguments to pass to the tool + grants (list[OAuthGrantInfo]): Active OAuth grants, newest first """ - name: str - arguments: MCPToolCallArguments | Unset = UNSET + grants: list[OAuthGrantInfo] additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: - name = self.name - - arguments: dict[str, Any] | Unset = UNSET - if not isinstance(self.arguments, Unset): - arguments = self.arguments.to_dict() + grants = [] + for grants_item_data in self.grants: + grants_item = grants_item_data.to_dict() + grants.append(grants_item) field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { - "name": name, + "grants": grants, } ) - if arguments is not UNSET: - field_dict["arguments"] = arguments return field_dict @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.mcp_tool_call_arguments import MCPToolCallArguments + from ..models.o_auth_grant_info import OAuthGrantInfo d = dict(src_dict) - name = d.pop("name") - - _arguments = d.pop("arguments", UNSET) - arguments: MCPToolCallArguments | Unset - if isinstance(_arguments, Unset): - arguments = UNSET - else: - arguments = MCPToolCallArguments.from_dict(_arguments) - - mcp_tool_call = cls( - name=name, - arguments=arguments, + grants = [] + _grants = d.pop("grants") + for grants_item_data in _grants: + grants_item = OAuthGrantInfo.from_dict(grants_item_data) + + grants.append(grants_item) + + o_auth_grants_response = cls( + grants=grants, ) - mcp_tool_call.additional_properties = d - return mcp_tool_call + o_auth_grants_response.additional_properties = d + return o_auth_grants_response @property def additional_keys(self) -> list[str]: diff --git a/robosystems_client/models/operation_envelope_reconciling_item_plan.py b/robosystems_client/models/operation_envelope_reconciling_item_plan.py new file mode 100644 index 0000000..7840975 --- /dev/null +++ b/robosystems_client/models/operation_envelope_reconciling_item_plan.py @@ -0,0 +1,158 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..models.operation_envelope_reconciling_item_plan_status import ( + OperationEnvelopeReconcilingItemPlanStatus, +) +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.reconciling_item_plan import ReconcilingItemPlan + + +T = TypeVar("T", bound="OperationEnvelopeReconcilingItemPlan") + + +@_attrs_define +class OperationEnvelopeReconcilingItemPlan: + """ + Attributes: + operation (str): Kebab-case operation name + operation_id (str): op_-prefixed ULID for audit and SSE correlation + status (OperationEnvelopeReconcilingItemPlanStatus): Operation lifecycle state + at (str): ISO-8601 UTC timestamp + result (None | ReconcilingItemPlan | Unset): Command-specific result payload + created_by (None | str | Unset): User ID that initiated the operation (null for legacy callers) + idempotent_replay (bool | Unset): True when this envelope came from the idempotency cache — the underlying + command did not execute again. False on fresh executions. Default: False. + """ + + operation: str + operation_id: str + status: OperationEnvelopeReconcilingItemPlanStatus + at: str + result: None | ReconcilingItemPlan | Unset = UNSET + created_by: None | str | Unset = UNSET + idempotent_replay: bool | Unset = False + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + from ..models.reconciling_item_plan import ReconcilingItemPlan + + operation = self.operation + + operation_id = self.operation_id + + status = self.status.value + + at = self.at + + result: dict[str, Any] | None | Unset + if isinstance(self.result, Unset): + result = UNSET + elif isinstance(self.result, ReconcilingItemPlan): + result = self.result.to_dict() + else: + result = self.result + + created_by: None | str | Unset + if isinstance(self.created_by, Unset): + created_by = UNSET + else: + created_by = self.created_by + + idempotent_replay = self.idempotent_replay + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "operation": operation, + "operationId": operation_id, + "status": status, + "at": at, + } + ) + if result is not UNSET: + field_dict["result"] = result + if created_by is not UNSET: + field_dict["createdBy"] = created_by + if idempotent_replay is not UNSET: + field_dict["idempotentReplay"] = idempotent_replay + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.reconciling_item_plan import ReconcilingItemPlan + + d = dict(src_dict) + operation = d.pop("operation") + + operation_id = d.pop("operationId") + + status = OperationEnvelopeReconcilingItemPlanStatus(d.pop("status")) + + at = d.pop("at") + + def _parse_result(data: object) -> None | ReconcilingItemPlan | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + result_type_0 = ReconcilingItemPlan.from_dict(data) + + return result_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(None | ReconcilingItemPlan | Unset, data) + + result = _parse_result(d.pop("result", UNSET)) + + def _parse_created_by(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + created_by = _parse_created_by(d.pop("createdBy", UNSET)) + + idempotent_replay = d.pop("idempotentReplay", UNSET) + + operation_envelope_reconciling_item_plan = cls( + operation=operation, + operation_id=operation_id, + status=status, + at=at, + result=result, + created_by=created_by, + idempotent_replay=idempotent_replay, + ) + + operation_envelope_reconciling_item_plan.additional_properties = d + return operation_envelope_reconciling_item_plan + + @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/operation_envelope_reconciling_item_plan_status.py b/robosystems_client/models/operation_envelope_reconciling_item_plan_status.py new file mode 100644 index 0000000..7337496 --- /dev/null +++ b/robosystems_client/models/operation_envelope_reconciling_item_plan_status.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class OperationEnvelopeReconcilingItemPlanStatus(str, Enum): + COMPLETED = "completed" + FAILED = "failed" + PENDING = "pending" + + def __str__(self) -> str: + return str(self.value) diff --git a/robosystems_client/models/operation_envelope_resolve_reconciling_item_response.py b/robosystems_client/models/operation_envelope_resolve_reconciling_item_response.py new file mode 100644 index 0000000..5027857 --- /dev/null +++ b/robosystems_client/models/operation_envelope_resolve_reconciling_item_response.py @@ -0,0 +1,162 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..models.operation_envelope_resolve_reconciling_item_response_status import ( + OperationEnvelopeResolveReconcilingItemResponseStatus, +) +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.resolve_reconciling_item_response import ResolveReconcilingItemResponse + + +T = TypeVar("T", bound="OperationEnvelopeResolveReconcilingItemResponse") + + +@_attrs_define +class OperationEnvelopeResolveReconcilingItemResponse: + """ + Attributes: + operation (str): Kebab-case operation name + operation_id (str): op_-prefixed ULID for audit and SSE correlation + status (OperationEnvelopeResolveReconcilingItemResponseStatus): Operation lifecycle state + at (str): ISO-8601 UTC timestamp + result (None | ResolveReconcilingItemResponse | Unset): Command-specific result payload + created_by (None | str | Unset): User ID that initiated the operation (null for legacy callers) + idempotent_replay (bool | Unset): True when this envelope came from the idempotency cache — the underlying + command did not execute again. False on fresh executions. Default: False. + """ + + operation: str + operation_id: str + status: OperationEnvelopeResolveReconcilingItemResponseStatus + at: str + result: None | ResolveReconcilingItemResponse | Unset = UNSET + created_by: None | str | Unset = UNSET + idempotent_replay: bool | Unset = False + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + from ..models.resolve_reconciling_item_response import ( + ResolveReconcilingItemResponse, + ) + + operation = self.operation + + operation_id = self.operation_id + + status = self.status.value + + at = self.at + + result: dict[str, Any] | None | Unset + if isinstance(self.result, Unset): + result = UNSET + elif isinstance(self.result, ResolveReconcilingItemResponse): + result = self.result.to_dict() + else: + result = self.result + + created_by: None | str | Unset + if isinstance(self.created_by, Unset): + created_by = UNSET + else: + created_by = self.created_by + + idempotent_replay = self.idempotent_replay + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "operation": operation, + "operationId": operation_id, + "status": status, + "at": at, + } + ) + if result is not UNSET: + field_dict["result"] = result + if created_by is not UNSET: + field_dict["createdBy"] = created_by + if idempotent_replay is not UNSET: + field_dict["idempotentReplay"] = idempotent_replay + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.resolve_reconciling_item_response import ( + ResolveReconcilingItemResponse, + ) + + d = dict(src_dict) + operation = d.pop("operation") + + operation_id = d.pop("operationId") + + status = OperationEnvelopeResolveReconcilingItemResponseStatus(d.pop("status")) + + at = d.pop("at") + + def _parse_result(data: object) -> None | ResolveReconcilingItemResponse | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + result_type_0 = ResolveReconcilingItemResponse.from_dict(data) + + return result_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(None | ResolveReconcilingItemResponse | Unset, data) + + result = _parse_result(d.pop("result", UNSET)) + + def _parse_created_by(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + created_by = _parse_created_by(d.pop("createdBy", UNSET)) + + idempotent_replay = d.pop("idempotentReplay", UNSET) + + operation_envelope_resolve_reconciling_item_response = cls( + operation=operation, + operation_id=operation_id, + status=status, + at=at, + result=result, + created_by=created_by, + idempotent_replay=idempotent_replay, + ) + + operation_envelope_resolve_reconciling_item_response.additional_properties = d + return operation_envelope_resolve_reconciling_item_response + + @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/operation_envelope_resolve_reconciling_item_response_status.py b/robosystems_client/models/operation_envelope_resolve_reconciling_item_response_status.py new file mode 100644 index 0000000..91161f2 --- /dev/null +++ b/robosystems_client/models/operation_envelope_resolve_reconciling_item_response_status.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class OperationEnvelopeResolveReconcilingItemResponseStatus(str, Enum): + COMPLETED = "completed" + FAILED = "failed" + PENDING = "pending" + + def __str__(self) -> str: + return str(self.value) diff --git a/robosystems_client/models/mcp_tool_call_arguments.py b/robosystems_client/models/preview_reconciling_item_request.py similarity index 63% rename from robosystems_client/models/mcp_tool_call_arguments.py rename to robosystems_client/models/preview_reconciling_item_request.py index cd78bd0..ac00e19 100644 --- a/robosystems_client/models/mcp_tool_call_arguments.py +++ b/robosystems_client/models/preview_reconciling_item_request.py @@ -6,29 +6,44 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field -T = TypeVar("T", bound="MCPToolCallArguments") +T = TypeVar("T", bound="PreviewReconcilingItemRequest") @_attrs_define -class MCPToolCallArguments: - """Arguments to pass to the tool""" +class PreviewReconcilingItemRequest: + """Read what changed on a reconciling item, and what resolving it would do. + Attributes: + event_id (str): Event id (evt_ prefixed) to inspect + """ + + event_id: str additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: + event_id = self.event_id field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) + field_dict.update( + { + "event_id": event_id, + } + ) return field_dict @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) - mcp_tool_call_arguments = cls() + event_id = d.pop("event_id") + + preview_reconciling_item_request = cls( + event_id=event_id, + ) - mcp_tool_call_arguments.additional_properties = d - return mcp_tool_call_arguments + preview_reconciling_item_request.additional_properties = d + return preview_reconciling_item_request @property def additional_keys(self) -> list[str]: diff --git a/robosystems_client/models/reconciling_item_catch_up.py b/robosystems_client/models/reconciling_item_catch_up.py new file mode 100644 index 0000000..cd02726 --- /dev/null +++ b/robosystems_client/models/reconciling_item_catch_up.py @@ -0,0 +1,121 @@ +from __future__ import annotations + +import datetime +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ReconcilingItemCatchUp") + + +@_attrs_define +class ReconcilingItemCatchUp: + """The catch-up entry a resolution posted. + + Attributes: + event_id (str): + posting_date (datetime.date): + status (str): + entry_id (None | str | Unset): + transaction_id (None | str | Unset): + """ + + event_id: str + posting_date: datetime.date + status: str + entry_id: None | str | Unset = UNSET + transaction_id: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + event_id = self.event_id + + posting_date = self.posting_date.isoformat() + + status = self.status + + entry_id: None | str | Unset + if isinstance(self.entry_id, Unset): + entry_id = UNSET + else: + entry_id = self.entry_id + + transaction_id: None | str | Unset + if isinstance(self.transaction_id, Unset): + transaction_id = UNSET + else: + transaction_id = self.transaction_id + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "event_id": event_id, + "posting_date": posting_date, + "status": status, + } + ) + if entry_id is not UNSET: + field_dict["entry_id"] = entry_id + if transaction_id is not UNSET: + field_dict["transaction_id"] = transaction_id + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + event_id = d.pop("event_id") + + posting_date = datetime.date.fromisoformat(d.pop("posting_date")) + + status = d.pop("status") + + def _parse_entry_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + entry_id = _parse_entry_id(d.pop("entry_id", UNSET)) + + def _parse_transaction_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + transaction_id = _parse_transaction_id(d.pop("transaction_id", UNSET)) + + reconciling_item_catch_up = cls( + event_id=event_id, + posting_date=posting_date, + status=status, + entry_id=entry_id, + transaction_id=transaction_id, + ) + + reconciling_item_catch_up.additional_properties = d + return reconciling_item_catch_up + + @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/reconciling_item_delta_line.py b/robosystems_client/models/reconciling_item_delta_line.py new file mode 100644 index 0000000..acd9f75 --- /dev/null +++ b/robosystems_client/models/reconciling_item_delta_line.py @@ -0,0 +1,166 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ReconcilingItemDeltaLine") + + +@_attrs_define +class ReconcilingItemDeltaLine: + """One account's net change between the posted entries and the new payload. + + Amounts are signed minor units in debit-positive convention: a positive + figure is a net debit, a negative one a net credit. ``delta`` is what a + catch-up entry would post to bring the books level. + + Attributes: + prior_net (int): Net of the posted entries, debit-positive + accepted_net (int): Net of the new payload, debit-positive + delta (int): accepted_net - prior_net + element_id (None | str | Unset): CoA element id; null when the account is unmapped + element_external_id (None | str | Unset): Source-system account id, when the line carried one + element_code (None | str | Unset): Account code + element_name (None | str | Unset): Account name + """ + + prior_net: int + accepted_net: int + delta: int + element_id: None | str | Unset = UNSET + element_external_id: None | str | Unset = UNSET + element_code: None | str | Unset = UNSET + element_name: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + prior_net = self.prior_net + + accepted_net = self.accepted_net + + delta = self.delta + + element_id: None | str | Unset + if isinstance(self.element_id, Unset): + element_id = UNSET + else: + element_id = self.element_id + + element_external_id: None | str | Unset + if isinstance(self.element_external_id, Unset): + element_external_id = UNSET + else: + element_external_id = self.element_external_id + + element_code: None | str | Unset + if isinstance(self.element_code, Unset): + element_code = UNSET + else: + element_code = self.element_code + + element_name: None | str | Unset + if isinstance(self.element_name, Unset): + element_name = UNSET + else: + element_name = self.element_name + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "prior_net": prior_net, + "accepted_net": accepted_net, + "delta": delta, + } + ) + if element_id is not UNSET: + field_dict["element_id"] = element_id + if element_external_id is not UNSET: + field_dict["element_external_id"] = element_external_id + if element_code is not UNSET: + field_dict["element_code"] = element_code + if element_name is not UNSET: + field_dict["element_name"] = element_name + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + prior_net = d.pop("prior_net") + + accepted_net = d.pop("accepted_net") + + delta = d.pop("delta") + + def _parse_element_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + element_id = _parse_element_id(d.pop("element_id", UNSET)) + + def _parse_element_external_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + element_external_id = _parse_element_external_id( + d.pop("element_external_id", UNSET) + ) + + def _parse_element_code(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + element_code = _parse_element_code(d.pop("element_code", UNSET)) + + def _parse_element_name(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + element_name = _parse_element_name(d.pop("element_name", UNSET)) + + reconciling_item_delta_line = cls( + prior_net=prior_net, + accepted_net=accepted_net, + delta=delta, + element_id=element_id, + element_external_id=element_external_id, + element_code=element_code, + element_name=element_name, + ) + + reconciling_item_delta_line.additional_properties = d + return reconciling_item_delta_line + + @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/reconciling_item_entry_summary.py b/robosystems_client/models/reconciling_item_entry_summary.py new file mode 100644 index 0000000..54f36ba --- /dev/null +++ b/robosystems_client/models/reconciling_item_entry_summary.py @@ -0,0 +1,183 @@ +from __future__ import annotations + +import datetime +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ReconcilingItemEntrySummary") + + +@_attrs_define +class ReconcilingItemEntrySummary: + """One entry on either side of the comparison. + + Attributes: + entry_id (None | str | Unset): Entry id; null for the accepted side, which is not posted yet + external_id (None | str | Unset): + posting_date (datetime.date | None | Unset): + memo (None | str | Unset): + status (None | str | Unset): Entry status; null on the accepted side + total_debit (int | Unset): Default: 0. + total_credit (int | Unset): Default: 0. + """ + + entry_id: None | str | Unset = UNSET + external_id: None | str | Unset = UNSET + posting_date: datetime.date | None | Unset = UNSET + memo: None | str | Unset = UNSET + status: None | str | Unset = UNSET + total_debit: int | Unset = 0 + total_credit: int | Unset = 0 + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + entry_id: None | str | Unset + if isinstance(self.entry_id, Unset): + entry_id = UNSET + else: + entry_id = self.entry_id + + external_id: None | str | Unset + if isinstance(self.external_id, Unset): + external_id = UNSET + else: + external_id = self.external_id + + posting_date: None | str | Unset + if isinstance(self.posting_date, Unset): + posting_date = UNSET + elif isinstance(self.posting_date, datetime.date): + posting_date = self.posting_date.isoformat() + else: + posting_date = self.posting_date + + memo: None | str | Unset + if isinstance(self.memo, Unset): + memo = UNSET + else: + memo = self.memo + + status: None | str | Unset + if isinstance(self.status, Unset): + status = UNSET + else: + status = self.status + + total_debit = self.total_debit + + total_credit = self.total_credit + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if entry_id is not UNSET: + field_dict["entry_id"] = entry_id + if external_id is not UNSET: + field_dict["external_id"] = external_id + if posting_date is not UNSET: + field_dict["posting_date"] = posting_date + if memo is not UNSET: + field_dict["memo"] = memo + if status is not UNSET: + field_dict["status"] = status + if total_debit is not UNSET: + field_dict["total_debit"] = total_debit + if total_credit is not UNSET: + field_dict["total_credit"] = total_credit + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + + def _parse_entry_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + entry_id = _parse_entry_id(d.pop("entry_id", UNSET)) + + def _parse_external_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + external_id = _parse_external_id(d.pop("external_id", UNSET)) + + def _parse_posting_date(data: object) -> datetime.date | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, str): + raise TypeError() + posting_date_type_0 = datetime.date.fromisoformat(data) + + return posting_date_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(datetime.date | None | Unset, data) + + posting_date = _parse_posting_date(d.pop("posting_date", UNSET)) + + def _parse_memo(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + memo = _parse_memo(d.pop("memo", UNSET)) + + def _parse_status(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + status = _parse_status(d.pop("status", UNSET)) + + total_debit = d.pop("total_debit", UNSET) + + total_credit = d.pop("total_credit", UNSET) + + reconciling_item_entry_summary = cls( + entry_id=entry_id, + external_id=external_id, + posting_date=posting_date, + memo=memo, + status=status, + total_debit=total_debit, + total_credit=total_credit, + ) + + reconciling_item_entry_summary.additional_properties = d + return reconciling_item_entry_summary + + @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/reconciling_item_plan.py b/robosystems_client/models/reconciling_item_plan.py new file mode 100644 index 0000000..16c518b --- /dev/null +++ b/robosystems_client/models/reconciling_item_plan.py @@ -0,0 +1,330 @@ +from __future__ import annotations + +import datetime +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..models.reconciling_item_plan_default_disposition import ( + ReconcilingItemPlanDefaultDisposition, +) +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.reconciling_item_delta_line import ReconcilingItemDeltaLine + from ..models.reconciling_item_entry_summary import ReconcilingItemEntrySummary + + +T = TypeVar("T", bound="ReconcilingItemPlan") + + +@_attrs_define +class ReconcilingItemPlan: + """What changed upstream, and what each disposition would do about it. + + Attributes: + event_id (str): + source (str): + event_type (str): + event_status (str): + default_disposition (ReconcilingItemPlanDefaultDisposition): What resolve would do with no disposition given: + restate while every affected period is open, catch_up once one is closed. + external_id (None | str | Unset): + drift_detected_at (datetime.datetime | None | Unset): When the sync first saw this difference + default_posting_date (datetime.date | None | Unset): Where a catch-up entry would land by default + affected_posting_dates (list[datetime.date] | Unset): Posting dates of the event's entries + closed_periods (list[str] | Unset): Names of closed periods the event's entries sit in + prior_entries (list[ReconcilingItemEntrySummary] | Unset): + accepted_entries (list[ReconcilingItemEntrySummary] | Unset): + delta (list[ReconcilingItemDeltaLine] | Unset): Per-account net change; empty when none + no_gl_effect (bool | Unset): The change moves no money — a memo or reference edit. catch_up posts nothing; + restate still regenerates so the entries carry the new text. Default: False. + restate_blockers (list[str] | Unset): Why restate is unavailable, if it is: a closed period, an entry that was + reversed or is not posted, or entries from elsewhere sharing this event's transaction. + unmapped_element_external_ids (list[str] | Unset): Accounts in the new payload with no mapping in this graph. + Both dispositions that write need them mapped first. + """ + + event_id: str + source: str + event_type: str + event_status: str + default_disposition: ReconcilingItemPlanDefaultDisposition + external_id: None | str | Unset = UNSET + drift_detected_at: datetime.datetime | None | Unset = UNSET + default_posting_date: datetime.date | None | Unset = UNSET + affected_posting_dates: list[datetime.date] | Unset = UNSET + closed_periods: list[str] | Unset = UNSET + prior_entries: list[ReconcilingItemEntrySummary] | Unset = UNSET + accepted_entries: list[ReconcilingItemEntrySummary] | Unset = UNSET + delta: list[ReconcilingItemDeltaLine] | Unset = UNSET + no_gl_effect: bool | Unset = False + restate_blockers: list[str] | Unset = UNSET + unmapped_element_external_ids: list[str] | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + event_id = self.event_id + + source = self.source + + event_type = self.event_type + + event_status = self.event_status + + default_disposition = self.default_disposition.value + + external_id: None | str | Unset + if isinstance(self.external_id, Unset): + external_id = UNSET + else: + external_id = self.external_id + + drift_detected_at: None | str | Unset + if isinstance(self.drift_detected_at, Unset): + drift_detected_at = UNSET + elif isinstance(self.drift_detected_at, datetime.datetime): + drift_detected_at = self.drift_detected_at.isoformat() + else: + drift_detected_at = self.drift_detected_at + + default_posting_date: None | str | Unset + if isinstance(self.default_posting_date, Unset): + default_posting_date = UNSET + elif isinstance(self.default_posting_date, datetime.date): + default_posting_date = self.default_posting_date.isoformat() + else: + default_posting_date = self.default_posting_date + + affected_posting_dates: list[str] | Unset = UNSET + if not isinstance(self.affected_posting_dates, Unset): + affected_posting_dates = [] + for affected_posting_dates_item_data in self.affected_posting_dates: + affected_posting_dates_item = affected_posting_dates_item_data.isoformat() + affected_posting_dates.append(affected_posting_dates_item) + + closed_periods: list[str] | Unset = UNSET + if not isinstance(self.closed_periods, Unset): + closed_periods = self.closed_periods + + prior_entries: list[dict[str, Any]] | Unset = UNSET + if not isinstance(self.prior_entries, Unset): + prior_entries = [] + for prior_entries_item_data in self.prior_entries: + prior_entries_item = prior_entries_item_data.to_dict() + prior_entries.append(prior_entries_item) + + accepted_entries: list[dict[str, Any]] | Unset = UNSET + if not isinstance(self.accepted_entries, Unset): + accepted_entries = [] + for accepted_entries_item_data in self.accepted_entries: + accepted_entries_item = accepted_entries_item_data.to_dict() + accepted_entries.append(accepted_entries_item) + + delta: list[dict[str, Any]] | Unset = UNSET + if not isinstance(self.delta, Unset): + delta = [] + for delta_item_data in self.delta: + delta_item = delta_item_data.to_dict() + delta.append(delta_item) + + no_gl_effect = self.no_gl_effect + + restate_blockers: list[str] | Unset = UNSET + if not isinstance(self.restate_blockers, Unset): + restate_blockers = self.restate_blockers + + unmapped_element_external_ids: list[str] | Unset = UNSET + if not isinstance(self.unmapped_element_external_ids, Unset): + unmapped_element_external_ids = self.unmapped_element_external_ids + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "event_id": event_id, + "source": source, + "event_type": event_type, + "event_status": event_status, + "default_disposition": default_disposition, + } + ) + if external_id is not UNSET: + field_dict["external_id"] = external_id + if drift_detected_at is not UNSET: + field_dict["drift_detected_at"] = drift_detected_at + if default_posting_date is not UNSET: + field_dict["default_posting_date"] = default_posting_date + if affected_posting_dates is not UNSET: + field_dict["affected_posting_dates"] = affected_posting_dates + if closed_periods is not UNSET: + field_dict["closed_periods"] = closed_periods + if prior_entries is not UNSET: + field_dict["prior_entries"] = prior_entries + if accepted_entries is not UNSET: + field_dict["accepted_entries"] = accepted_entries + if delta is not UNSET: + field_dict["delta"] = delta + if no_gl_effect is not UNSET: + field_dict["no_gl_effect"] = no_gl_effect + if restate_blockers is not UNSET: + field_dict["restate_blockers"] = restate_blockers + if unmapped_element_external_ids is not UNSET: + field_dict["unmapped_element_external_ids"] = unmapped_element_external_ids + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.reconciling_item_delta_line import ReconcilingItemDeltaLine + from ..models.reconciling_item_entry_summary import ReconcilingItemEntrySummary + + d = dict(src_dict) + event_id = d.pop("event_id") + + source = d.pop("source") + + event_type = d.pop("event_type") + + event_status = d.pop("event_status") + + default_disposition = ReconcilingItemPlanDefaultDisposition( + d.pop("default_disposition") + ) + + def _parse_external_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + external_id = _parse_external_id(d.pop("external_id", UNSET)) + + def _parse_drift_detected_at(data: object) -> datetime.datetime | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, str): + raise TypeError() + drift_detected_at_type_0 = datetime.datetime.fromisoformat(data) + + return drift_detected_at_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(datetime.datetime | None | Unset, data) + + drift_detected_at = _parse_drift_detected_at(d.pop("drift_detected_at", UNSET)) + + def _parse_default_posting_date(data: object) -> datetime.date | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, str): + raise TypeError() + default_posting_date_type_0 = datetime.date.fromisoformat(data) + + return default_posting_date_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(datetime.date | None | Unset, data) + + default_posting_date = _parse_default_posting_date( + d.pop("default_posting_date", UNSET) + ) + + _affected_posting_dates = d.pop("affected_posting_dates", UNSET) + affected_posting_dates: list[datetime.date] | Unset = UNSET + if _affected_posting_dates is not UNSET: + affected_posting_dates = [] + for affected_posting_dates_item_data in _affected_posting_dates: + affected_posting_dates_item = datetime.date.fromisoformat( + affected_posting_dates_item_data + ) + + affected_posting_dates.append(affected_posting_dates_item) + + closed_periods = cast(list[str], d.pop("closed_periods", UNSET)) + + _prior_entries = d.pop("prior_entries", UNSET) + prior_entries: list[ReconcilingItemEntrySummary] | Unset = UNSET + if _prior_entries is not UNSET: + prior_entries = [] + for prior_entries_item_data in _prior_entries: + prior_entries_item = ReconcilingItemEntrySummary.from_dict( + prior_entries_item_data + ) + + prior_entries.append(prior_entries_item) + + _accepted_entries = d.pop("accepted_entries", UNSET) + accepted_entries: list[ReconcilingItemEntrySummary] | Unset = UNSET + if _accepted_entries is not UNSET: + accepted_entries = [] + for accepted_entries_item_data in _accepted_entries: + accepted_entries_item = ReconcilingItemEntrySummary.from_dict( + accepted_entries_item_data + ) + + accepted_entries.append(accepted_entries_item) + + _delta = d.pop("delta", UNSET) + delta: list[ReconcilingItemDeltaLine] | Unset = UNSET + if _delta is not UNSET: + delta = [] + for delta_item_data in _delta: + delta_item = ReconcilingItemDeltaLine.from_dict(delta_item_data) + + delta.append(delta_item) + + no_gl_effect = d.pop("no_gl_effect", UNSET) + + restate_blockers = cast(list[str], d.pop("restate_blockers", UNSET)) + + unmapped_element_external_ids = cast( + list[str], d.pop("unmapped_element_external_ids", UNSET) + ) + + reconciling_item_plan = cls( + event_id=event_id, + source=source, + event_type=event_type, + event_status=event_status, + default_disposition=default_disposition, + external_id=external_id, + drift_detected_at=drift_detected_at, + default_posting_date=default_posting_date, + affected_posting_dates=affected_posting_dates, + closed_periods=closed_periods, + prior_entries=prior_entries, + accepted_entries=accepted_entries, + delta=delta, + no_gl_effect=no_gl_effect, + restate_blockers=restate_blockers, + unmapped_element_external_ids=unmapped_element_external_ids, + ) + + reconciling_item_plan.additional_properties = d + return reconciling_item_plan + + @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/reconciling_item_plan_default_disposition.py b/robosystems_client/models/reconciling_item_plan_default_disposition.py new file mode 100644 index 0000000..7fdb3ba --- /dev/null +++ b/robosystems_client/models/reconciling_item_plan_default_disposition.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class ReconcilingItemPlanDefaultDisposition(str, Enum): + ACKNOWLEDGE = "acknowledge" + CATCH_UP = "catch_up" + RESTATE = "restate" + + def __str__(self) -> str: + return str(self.value) diff --git a/robosystems_client/models/reconciling_item_regenerated.py b/robosystems_client/models/reconciling_item_regenerated.py new file mode 100644 index 0000000..1629113 --- /dev/null +++ b/robosystems_client/models/reconciling_item_regenerated.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ReconcilingItemRegenerated") + + +@_attrs_define +class ReconcilingItemRegenerated: + """The entries a restate rebuilt. + + Attributes: + transaction_ids (list[str] | Unset): + entry_ids (list[str] | Unset): + """ + + transaction_ids: list[str] | Unset = UNSET + entry_ids: list[str] | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + transaction_ids: list[str] | Unset = UNSET + if not isinstance(self.transaction_ids, Unset): + transaction_ids = self.transaction_ids + + entry_ids: list[str] | Unset = UNSET + if not isinstance(self.entry_ids, Unset): + entry_ids = self.entry_ids + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if transaction_ids is not UNSET: + field_dict["transaction_ids"] = transaction_ids + if entry_ids is not UNSET: + field_dict["entry_ids"] = entry_ids + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + transaction_ids = cast(list[str], d.pop("transaction_ids", UNSET)) + + entry_ids = cast(list[str], d.pop("entry_ids", UNSET)) + + reconciling_item_regenerated = cls( + transaction_ids=transaction_ids, + entry_ids=entry_ids, + ) + + reconciling_item_regenerated.additional_properties = d + return reconciling_item_regenerated + + @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/resolve_reconciling_item_request.py b/robosystems_client/models/resolve_reconciling_item_request.py new file mode 100644 index 0000000..c98348d --- /dev/null +++ b/robosystems_client/models/resolve_reconciling_item_request.py @@ -0,0 +1,198 @@ +from __future__ import annotations + +import datetime +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..models.resolve_reconciling_item_request_disposition_type_0 import ( + ResolveReconcilingItemRequestDispositionType0, +) +from ..models.resolve_reconciling_item_request_status import ( + ResolveReconcilingItemRequestStatus, +) +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ResolveReconcilingItemRequest") + + +@_attrs_define +class ResolveReconcilingItemRequest: + """Dispose of one reconciling item and clear its flag. + + Attributes: + event_id (str): Event id (evt_ prefixed) to resolve + disposition (None | ResolveReconcilingItemRequestDispositionType0 | Unset): How to dispose of the difference. + Omit to take the default the preview reports: restate when every period the event touches is open, catch_up when + any is closed. + posting_date (datetime.date | None | Unset): catch_up only: when to post the catch-up entry. Defaults to the end + of the earliest open period. + status (ResolveReconcilingItemRequestStatus | Unset): catch_up only: whether the catch-up entry is drafted for + review at close (default) or posted immediately. A draft appears in list-period-drafts and posts locally when + the period closes. Default: ResolveReconcilingItemRequestStatus.DRAFT. + note (None | str | Unset): Why this disposition. Required for acknowledge, where it is the only record of what + was done instead. + reference_event_id (None | str | Unset): acknowledge only: the event that already handled this difference (e.g. + an alignment entry authored by hand), recorded on the trail. + """ + + event_id: str + disposition: None | ResolveReconcilingItemRequestDispositionType0 | Unset = UNSET + posting_date: datetime.date | None | Unset = UNSET + status: ResolveReconcilingItemRequestStatus | Unset = ( + ResolveReconcilingItemRequestStatus.DRAFT + ) + note: None | str | Unset = UNSET + reference_event_id: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + event_id = self.event_id + + disposition: None | str | Unset + if isinstance(self.disposition, Unset): + disposition = UNSET + elif isinstance(self.disposition, ResolveReconcilingItemRequestDispositionType0): + disposition = self.disposition.value + else: + disposition = self.disposition + + posting_date: None | str | Unset + if isinstance(self.posting_date, Unset): + posting_date = UNSET + elif isinstance(self.posting_date, datetime.date): + posting_date = self.posting_date.isoformat() + else: + posting_date = self.posting_date + + status: str | Unset = UNSET + if not isinstance(self.status, Unset): + status = self.status.value + + note: None | str | Unset + if isinstance(self.note, Unset): + note = UNSET + else: + note = self.note + + reference_event_id: None | str | Unset + if isinstance(self.reference_event_id, Unset): + reference_event_id = UNSET + else: + reference_event_id = self.reference_event_id + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "event_id": event_id, + } + ) + if disposition is not UNSET: + field_dict["disposition"] = disposition + if posting_date is not UNSET: + field_dict["posting_date"] = posting_date + if status is not UNSET: + field_dict["status"] = status + if note is not UNSET: + field_dict["note"] = note + if reference_event_id is not UNSET: + field_dict["reference_event_id"] = reference_event_id + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + event_id = d.pop("event_id") + + def _parse_disposition( + data: object, + ) -> None | ResolveReconcilingItemRequestDispositionType0 | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, str): + raise TypeError() + disposition_type_0 = ResolveReconcilingItemRequestDispositionType0(data) + + return disposition_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(None | ResolveReconcilingItemRequestDispositionType0 | Unset, data) + + disposition = _parse_disposition(d.pop("disposition", UNSET)) + + def _parse_posting_date(data: object) -> datetime.date | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, str): + raise TypeError() + posting_date_type_0 = datetime.date.fromisoformat(data) + + return posting_date_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(datetime.date | None | Unset, data) + + posting_date = _parse_posting_date(d.pop("posting_date", UNSET)) + + _status = d.pop("status", UNSET) + status: ResolveReconcilingItemRequestStatus | Unset + if isinstance(_status, Unset): + status = UNSET + else: + status = ResolveReconcilingItemRequestStatus(_status) + + def _parse_note(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + note = _parse_note(d.pop("note", UNSET)) + + def _parse_reference_event_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + reference_event_id = _parse_reference_event_id(d.pop("reference_event_id", UNSET)) + + resolve_reconciling_item_request = cls( + event_id=event_id, + disposition=disposition, + posting_date=posting_date, + status=status, + note=note, + reference_event_id=reference_event_id, + ) + + resolve_reconciling_item_request.additional_properties = d + return resolve_reconciling_item_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/resolve_reconciling_item_request_disposition_type_0.py b/robosystems_client/models/resolve_reconciling_item_request_disposition_type_0.py new file mode 100644 index 0000000..6ca71e4 --- /dev/null +++ b/robosystems_client/models/resolve_reconciling_item_request_disposition_type_0.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class ResolveReconcilingItemRequestDispositionType0(str, Enum): + ACKNOWLEDGE = "acknowledge" + CATCH_UP = "catch_up" + RESTATE = "restate" + + def __str__(self) -> str: + return str(self.value) diff --git a/robosystems_client/models/resolve_reconciling_item_request_status.py b/robosystems_client/models/resolve_reconciling_item_request_status.py new file mode 100644 index 0000000..3aec13c --- /dev/null +++ b/robosystems_client/models/resolve_reconciling_item_request_status.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class ResolveReconcilingItemRequestStatus(str, Enum): + DRAFT = "draft" + POSTED = "posted" + + def __str__(self) -> str: + return str(self.value) diff --git a/robosystems_client/models/resolve_reconciling_item_response.py b/robosystems_client/models/resolve_reconciling_item_response.py new file mode 100644 index 0000000..691b63b --- /dev/null +++ b/robosystems_client/models/resolve_reconciling_item_response.py @@ -0,0 +1,256 @@ +from __future__ import annotations + +import datetime +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..models.resolve_reconciling_item_response_disposition import ( + ResolveReconcilingItemResponseDisposition, +) +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.reconciling_item_catch_up import ReconcilingItemCatchUp + from ..models.reconciling_item_delta_line import ReconcilingItemDeltaLine + from ..models.reconciling_item_regenerated import ReconcilingItemRegenerated + + +T = TypeVar("T", bound="ResolveReconcilingItemResponse") + + +@_attrs_define +class ResolveReconcilingItemResponse: + """The outcome of resolving one reconciling item. + + Attributes: + event_id (str): + disposition (ResolveReconcilingItemResponseDisposition): + resolved_at (datetime.datetime): + resolved_by (str): + external_id (None | str | Unset): + delta (list[ReconcilingItemDeltaLine] | Unset): + no_gl_effect (bool | Unset): Default: False. + catch_up (None | ReconcilingItemCatchUp | Unset): Present when the disposition posted a catch-up entry + regenerated (None | ReconcilingItemRegenerated | Unset): Present when the disposition rebuilt the event's + entries + reference_event_id (None | str | Unset): + note (None | str | Unset): + """ + + event_id: str + disposition: ResolveReconcilingItemResponseDisposition + resolved_at: datetime.datetime + resolved_by: str + external_id: None | str | Unset = UNSET + delta: list[ReconcilingItemDeltaLine] | Unset = UNSET + no_gl_effect: bool | Unset = False + catch_up: None | ReconcilingItemCatchUp | Unset = UNSET + regenerated: None | ReconcilingItemRegenerated | Unset = UNSET + reference_event_id: None | str | Unset = UNSET + note: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + from ..models.reconciling_item_catch_up import ReconcilingItemCatchUp + from ..models.reconciling_item_regenerated import ReconcilingItemRegenerated + + event_id = self.event_id + + disposition = self.disposition.value + + resolved_at = self.resolved_at.isoformat() + + resolved_by = self.resolved_by + + external_id: None | str | Unset + if isinstance(self.external_id, Unset): + external_id = UNSET + else: + external_id = self.external_id + + delta: list[dict[str, Any]] | Unset = UNSET + if not isinstance(self.delta, Unset): + delta = [] + for delta_item_data in self.delta: + delta_item = delta_item_data.to_dict() + delta.append(delta_item) + + no_gl_effect = self.no_gl_effect + + catch_up: dict[str, Any] | None | Unset + if isinstance(self.catch_up, Unset): + catch_up = UNSET + elif isinstance(self.catch_up, ReconcilingItemCatchUp): + catch_up = self.catch_up.to_dict() + else: + catch_up = self.catch_up + + regenerated: dict[str, Any] | None | Unset + if isinstance(self.regenerated, Unset): + regenerated = UNSET + elif isinstance(self.regenerated, ReconcilingItemRegenerated): + regenerated = self.regenerated.to_dict() + else: + regenerated = self.regenerated + + reference_event_id: None | str | Unset + if isinstance(self.reference_event_id, Unset): + reference_event_id = UNSET + else: + reference_event_id = self.reference_event_id + + note: None | str | Unset + if isinstance(self.note, Unset): + note = UNSET + else: + note = self.note + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "event_id": event_id, + "disposition": disposition, + "resolved_at": resolved_at, + "resolved_by": resolved_by, + } + ) + if external_id is not UNSET: + field_dict["external_id"] = external_id + if delta is not UNSET: + field_dict["delta"] = delta + if no_gl_effect is not UNSET: + field_dict["no_gl_effect"] = no_gl_effect + if catch_up is not UNSET: + field_dict["catch_up"] = catch_up + if regenerated is not UNSET: + field_dict["regenerated"] = regenerated + if reference_event_id is not UNSET: + field_dict["reference_event_id"] = reference_event_id + if note is not UNSET: + field_dict["note"] = note + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.reconciling_item_catch_up import ReconcilingItemCatchUp + from ..models.reconciling_item_delta_line import ReconcilingItemDeltaLine + from ..models.reconciling_item_regenerated import ReconcilingItemRegenerated + + d = dict(src_dict) + event_id = d.pop("event_id") + + disposition = ResolveReconcilingItemResponseDisposition(d.pop("disposition")) + + resolved_at = datetime.datetime.fromisoformat(d.pop("resolved_at")) + + resolved_by = d.pop("resolved_by") + + def _parse_external_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + external_id = _parse_external_id(d.pop("external_id", UNSET)) + + _delta = d.pop("delta", UNSET) + delta: list[ReconcilingItemDeltaLine] | Unset = UNSET + if _delta is not UNSET: + delta = [] + for delta_item_data in _delta: + delta_item = ReconcilingItemDeltaLine.from_dict(delta_item_data) + + delta.append(delta_item) + + no_gl_effect = d.pop("no_gl_effect", UNSET) + + def _parse_catch_up(data: object) -> None | ReconcilingItemCatchUp | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + catch_up_type_0 = ReconcilingItemCatchUp.from_dict(data) + + return catch_up_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(None | ReconcilingItemCatchUp | Unset, data) + + catch_up = _parse_catch_up(d.pop("catch_up", UNSET)) + + def _parse_regenerated(data: object) -> None | ReconcilingItemRegenerated | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + regenerated_type_0 = ReconcilingItemRegenerated.from_dict(data) + + return regenerated_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(None | ReconcilingItemRegenerated | Unset, data) + + regenerated = _parse_regenerated(d.pop("regenerated", UNSET)) + + def _parse_reference_event_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + reference_event_id = _parse_reference_event_id(d.pop("reference_event_id", UNSET)) + + def _parse_note(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + note = _parse_note(d.pop("note", UNSET)) + + resolve_reconciling_item_response = cls( + event_id=event_id, + disposition=disposition, + resolved_at=resolved_at, + resolved_by=resolved_by, + external_id=external_id, + delta=delta, + no_gl_effect=no_gl_effect, + catch_up=catch_up, + regenerated=regenerated, + reference_event_id=reference_event_id, + note=note, + ) + + resolve_reconciling_item_response.additional_properties = d + return resolve_reconciling_item_response + + @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/resolve_reconciling_item_response_disposition.py b/robosystems_client/models/resolve_reconciling_item_response_disposition.py new file mode 100644 index 0000000..b1f4f72 --- /dev/null +++ b/robosystems_client/models/resolve_reconciling_item_response_disposition.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class ResolveReconcilingItemResponseDisposition(str, Enum): + ACKNOWLEDGE = "acknowledge" + CATCH_UP = "catch_up" + RESTATE = "restate" + + def __str__(self) -> str: + return str(self.value)