diff --git a/robosystems_client/api/content_operations/complete_document_upload.py b/robosystems_client/api/content_operations/complete_document_upload.py new file mode 100644 index 00000000..73037c4c --- /dev/null +++ b/robosystems_client/api/content_operations/complete_document_upload.py @@ -0,0 +1,264 @@ +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.complete_document_upload_op import CompleteDocumentUploadOp +from ...models.error_response import ErrorResponse +from ...models.operation_envelope import OperationEnvelope +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + graph_id: str, + *, + body: CompleteDocumentUploadOp, + 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": "/v1/graphs/{graph_id}/operations/complete-document-upload".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 | OperationEnvelope | None: + if response.status_code == 200: + response_200 = OperationEnvelope.from_dict(response.json()) + + return response_200 + + if response.status_code == 400: + response_400 = ErrorResponse.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = ErrorResponse.from_dict(response.json()) + + return response_401 + + if response.status_code == 403: + response_403 = ErrorResponse.from_dict(response.json()) + + return response_403 + + if response.status_code == 404: + response_404 = ErrorResponse.from_dict(response.json()) + + return response_404 + + if response.status_code == 409: + response_409 = ErrorResponse.from_dict(response.json()) + + return response_409 + + if response.status_code == 422: + response_422 = 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 | OperationEnvelope]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + body: CompleteDocumentUploadOp, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelope]: + """Complete a Document File Upload + + Store an uploaded file as a document. The uploaded bytes are checked against the file's type and + size cap and hashed (SHA-256); a file that fails the check is discarded. Completing the same upload + again returns the document it created. Returns the document. + + **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 (CompleteDocumentUploadOp): Body for complete-document-upload: store the uploaded + file as a document. + + 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 | OperationEnvelope] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + body=body, + idempotency_key=idempotency_key, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + graph_id: str, + *, + client: AuthenticatedClient, + body: CompleteDocumentUploadOp, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelope | None: + """Complete a Document File Upload + + Store an uploaded file as a document. The uploaded bytes are checked against the file's type and + size cap and hashed (SHA-256); a file that fails the check is discarded. Completing the same upload + again returns the document it created. Returns the document. + + **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 (CompleteDocumentUploadOp): Body for complete-document-upload: store the uploaded + file as a document. + + 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 | OperationEnvelope + """ + + return sync_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ).parsed + + +async def asyncio_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + body: CompleteDocumentUploadOp, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelope]: + """Complete a Document File Upload + + Store an uploaded file as a document. The uploaded bytes are checked against the file's type and + size cap and hashed (SHA-256); a file that fails the check is discarded. Completing the same upload + again returns the document it created. Returns the document. + + **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 (CompleteDocumentUploadOp): Body for complete-document-upload: store the uploaded + file as a document. + + 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 | OperationEnvelope] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + body=body, + idempotency_key=idempotency_key, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + graph_id: str, + *, + client: AuthenticatedClient, + body: CompleteDocumentUploadOp, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelope | None: + """Complete a Document File Upload + + Store an uploaded file as a document. The uploaded bytes are checked against the file's type and + size cap and hashed (SHA-256); a file that fails the check is discarded. Completing the same upload + again returns the document it created. Returns the document. + + **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 (CompleteDocumentUploadOp): Body for complete-document-upload: store the uploaded + file as a document. + + 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 | OperationEnvelope + """ + + return ( + await asyncio_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ) + ).parsed diff --git a/robosystems_client/api/content_operations/create_document_upload.py b/robosystems_client/api/content_operations/create_document_upload.py new file mode 100644 index 00000000..57e1dad7 --- /dev/null +++ b/robosystems_client/api/content_operations/create_document_upload.py @@ -0,0 +1,276 @@ +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.create_document_upload_op import CreateDocumentUploadOp +from ...models.error_response import ErrorResponse +from ...models.operation_envelope import OperationEnvelope +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + graph_id: str, + *, + body: CreateDocumentUploadOp, + 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": "/v1/graphs/{graph_id}/operations/create-document-upload".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 | OperationEnvelope | None: + if response.status_code == 200: + response_200 = OperationEnvelope.from_dict(response.json()) + + return response_200 + + if response.status_code == 400: + response_400 = ErrorResponse.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = ErrorResponse.from_dict(response.json()) + + return response_401 + + if response.status_code == 403: + response_403 = ErrorResponse.from_dict(response.json()) + + return response_403 + + if response.status_code == 404: + response_404 = ErrorResponse.from_dict(response.json()) + + return response_404 + + if response.status_code == 409: + response_409 = ErrorResponse.from_dict(response.json()) + + return response_409 + + if response.status_code == 422: + response_422 = 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 | OperationEnvelope]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + body: CreateDocumentUploadOp, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelope]: + """Presign a Document File Upload + + Start uploading a document file: a PDF (a bank statement kept as evidence for a recorded balance, an + invoice) or a PNG or JPEG photo (a receipt). Returns an upload id and a presigned URL: PUT the file + there with the same Content-Type (and Content-Length, if declared), then call `complete-document- + upload`, which creates the document. Nothing is recorded until then, and an upload never completed + expires. A stored file is not indexed for search, does not count toward the plan's document limit, + and never changes once stored. + + **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 (CreateDocumentUploadOp): Body for create-document-upload: where to upload a + document's file. + + 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 | OperationEnvelope] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + body=body, + idempotency_key=idempotency_key, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + graph_id: str, + *, + client: AuthenticatedClient, + body: CreateDocumentUploadOp, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelope | None: + """Presign a Document File Upload + + Start uploading a document file: a PDF (a bank statement kept as evidence for a recorded balance, an + invoice) or a PNG or JPEG photo (a receipt). Returns an upload id and a presigned URL: PUT the file + there with the same Content-Type (and Content-Length, if declared), then call `complete-document- + upload`, which creates the document. Nothing is recorded until then, and an upload never completed + expires. A stored file is not indexed for search, does not count toward the plan's document limit, + and never changes once stored. + + **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 (CreateDocumentUploadOp): Body for create-document-upload: where to upload a + document's file. + + 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 | OperationEnvelope + """ + + return sync_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ).parsed + + +async def asyncio_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + body: CreateDocumentUploadOp, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelope]: + """Presign a Document File Upload + + Start uploading a document file: a PDF (a bank statement kept as evidence for a recorded balance, an + invoice) or a PNG or JPEG photo (a receipt). Returns an upload id and a presigned URL: PUT the file + there with the same Content-Type (and Content-Length, if declared), then call `complete-document- + upload`, which creates the document. Nothing is recorded until then, and an upload never completed + expires. A stored file is not indexed for search, does not count toward the plan's document limit, + and never changes once stored. + + **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 (CreateDocumentUploadOp): Body for create-document-upload: where to upload a + document's file. + + 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 | OperationEnvelope] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + body=body, + idempotency_key=idempotency_key, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + graph_id: str, + *, + client: AuthenticatedClient, + body: CreateDocumentUploadOp, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelope | None: + """Presign a Document File Upload + + Start uploading a document file: a PDF (a bank statement kept as evidence for a recorded balance, an + invoice) or a PNG or JPEG photo (a receipt). Returns an upload id and a presigned URL: PUT the file + there with the same Content-Type (and Content-Length, if declared), then call `complete-document- + upload`, which creates the document. Nothing is recorded until then, and an upload never completed + expires. A stored file is not indexed for search, does not count toward the plan's document limit, + and never changes once stored. + + **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 (CreateDocumentUploadOp): Body for create-document-upload: where to upload a + document's file. + + 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 | OperationEnvelope + """ + + return ( + await asyncio_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ) + ).parsed diff --git a/robosystems_client/api/documents/get_document_file.py b/robosystems_client/api/documents/get_document_file.py new file mode 100644 index 00000000..eea44205 --- /dev/null +++ b/robosystems_client/api/documents/get_document_file.py @@ -0,0 +1,214 @@ +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.document_file_download_response import DocumentFileDownloadResponse +from ...models.error_response import ErrorResponse +from ...models.http_validation_error import HTTPValidationError +from ...types import Response + + +def _get_kwargs( + graph_id: str, + document_id: str, +) -> dict[str, Any]: + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/v1/graphs/{graph_id}/documents/{document_id}/file".format( + graph_id=quote(str(graph_id), safe=""), + document_id=quote(str(document_id), safe=""), + ), + } + + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> DocumentFileDownloadResponse | ErrorResponse | HTTPValidationError | None: + if response.status_code == 200: + response_200 = DocumentFileDownloadResponse.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 == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if response.status_code == 429: + response_429 = ErrorResponse.from_dict(response.json()) + + return response_429 + + if response.status_code == 500: + response_500 = ErrorResponse.from_dict(response.json()) + + return response_500 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[DocumentFileDownloadResponse | 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, + document_id: str, + *, + client: AuthenticatedClient, +) -> Response[DocumentFileDownloadResponse | ErrorResponse | HTTPValidationError]: + """Download Document File + + A short-lived link to a document's stored file, such as the statement PDF behind a recorded balance. + + Args: + graph_id (str): + document_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[DocumentFileDownloadResponse | ErrorResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + document_id=document_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + graph_id: str, + document_id: str, + *, + client: AuthenticatedClient, +) -> DocumentFileDownloadResponse | ErrorResponse | HTTPValidationError | None: + """Download Document File + + A short-lived link to a document's stored file, such as the statement PDF behind a recorded balance. + + Args: + graph_id (str): + document_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: + DocumentFileDownloadResponse | ErrorResponse | HTTPValidationError + """ + + return sync_detailed( + graph_id=graph_id, + document_id=document_id, + client=client, + ).parsed + + +async def asyncio_detailed( + graph_id: str, + document_id: str, + *, + client: AuthenticatedClient, +) -> Response[DocumentFileDownloadResponse | ErrorResponse | HTTPValidationError]: + """Download Document File + + A short-lived link to a document's stored file, such as the statement PDF behind a recorded balance. + + Args: + graph_id (str): + document_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[DocumentFileDownloadResponse | ErrorResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + document_id=document_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + graph_id: str, + document_id: str, + *, + client: AuthenticatedClient, +) -> DocumentFileDownloadResponse | ErrorResponse | HTTPValidationError | None: + """Download Document File + + A short-lived link to a document's stored file, such as the statement PDF behind a recorded balance. + + Args: + graph_id (str): + document_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: + DocumentFileDownloadResponse | ErrorResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + graph_id=graph_id, + document_id=document_id, + client=client, + ) + ).parsed diff --git a/robosystems_client/api/robo_ledger_ledger_events/record_statement_balance.py b/robosystems_client/api/robo_ledger_ledger_events/record_statement_balance.py index 8ff39a00..de43f76f 100644 --- a/robosystems_client/api/robo_ledger_ledger_events/record_statement_balance.py +++ b/robosystems_client/api/robo_ledger_ledger_events/record_statement_balance.py @@ -118,7 +118,8 @@ def sync_detailed( number in the account's normal direction, with the statement's ending date. The ledger's balance at that date is set beside it, counting the drafts the close will post, and the result is recorded on the account's statement reconciliation for the period the statement ends in. Attach the statement as - evidence by passing the `document_id` of a document added with create-document. Writes no books. The + evidence by passing the `document_id` of its stored file (create-document-upload, then complete- + document-upload); recording it again with another document lapses a sign-off. Writes no books. The first statement recorded for an account creates its reconciliation, which does not hold the close: turn `required_for_close` on with set-reconciliation-policy to make every period's close wait for a statement on that account. Recording the same account and date again replaces the earlier balance; @@ -171,7 +172,8 @@ def sync( number in the account's normal direction, with the statement's ending date. The ledger's balance at that date is set beside it, counting the drafts the close will post, and the result is recorded on the account's statement reconciliation for the period the statement ends in. Attach the statement as - evidence by passing the `document_id` of a document added with create-document. Writes no books. The + evidence by passing the `document_id` of its stored file (create-document-upload, then complete- + document-upload); recording it again with another document lapses a sign-off. Writes no books. The first statement recorded for an account creates its reconciliation, which does not hold the close: turn `required_for_close` on with set-reconciliation-policy to make every period's close wait for a statement on that account. Recording the same account and date again replaces the earlier balance; @@ -219,7 +221,8 @@ async def asyncio_detailed( number in the account's normal direction, with the statement's ending date. The ledger's balance at that date is set beside it, counting the drafts the close will post, and the result is recorded on the account's statement reconciliation for the period the statement ends in. Attach the statement as - evidence by passing the `document_id` of a document added with create-document. Writes no books. The + evidence by passing the `document_id` of its stored file (create-document-upload, then complete- + document-upload); recording it again with another document lapses a sign-off. Writes no books. The first statement recorded for an account creates its reconciliation, which does not hold the close: turn `required_for_close` on with set-reconciliation-policy to make every period's close wait for a statement on that account. Recording the same account and date again replaces the earlier balance; @@ -270,7 +273,8 @@ async def asyncio( number in the account's normal direction, with the statement's ending date. The ledger's balance at that date is set beside it, counting the drafts the close will post, and the result is recorded on the account's statement reconciliation for the period the statement ends in. Attach the statement as - evidence by passing the `document_id` of a document added with create-document. Writes no books. The + evidence by passing the `document_id` of its stored file (create-document-upload, then complete- + document-upload); recording it again with another document lapses a sign-off. Writes no books. The first statement recorded for an account creates its reconciliation, which does not hold the close: turn `required_for_close` on with set-reconciliation-policy to make every period's close wait for a statement on that account. Recording the same account and date again replaces the earlier balance; diff --git a/robosystems_client/api/robo_ledger_ledger_events/resolve_reconciling_item.py b/robosystems_client/api/robo_ledger_ledger_events/resolve_reconciling_item.py index a7752aee..4dd47d12 100644 --- a/robosystems_client/api/robo_ledger_ledger_events/resolve_reconciling_item.py +++ b/robosystems_client/api/robo_ledger_ledger_events/resolve_reconciling_item.py @@ -121,9 +121,11 @@ def sync_detailed( 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. + entry that handled it). An alignment entry authored by hand can name the items it settles in + metadata.resolves_reconciling_items instead, and each is acknowledged against it as it is recorded — + an item left flagged would be caught up a second time. 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. @@ -169,9 +171,11 @@ def sync( 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. + entry that handled it). An alignment entry authored by hand can name the items it settles in + metadata.resolves_reconciling_items instead, and each is acknowledged against it as it is recorded — + an item left flagged would be caught up a second time. 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. @@ -212,9 +216,11 @@ async def asyncio_detailed( 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. + entry that handled it). An alignment entry authored by hand can name the items it settles in + metadata.resolves_reconciling_items instead, and each is acknowledged against it as it is recorded — + an item left flagged would be caught up a second time. 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. @@ -258,9 +264,11 @@ async def asyncio( 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. + entry that handled it). An alignment entry authored by hand can name the items it settles in + metadata.resolves_reconciling_items instead, and each is acknowledged against it as it is recorded — + an item left flagged would be caught up a second time. 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. diff --git a/robosystems_client/api/robo_ledger_setup/change_calendar_start.py b/robosystems_client/api/robo_ledger_setup/change_calendar_start.py new file mode 100644 index 00000000..af90993c --- /dev/null +++ b/robosystems_client/api/robo_ledger_setup/change_calendar_start.py @@ -0,0 +1,296 @@ +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.change_calendar_start_request import ChangeCalendarStartRequest +from ...models.error_response import ErrorResponse +from ...models.operation_envelope_change_calendar_start_response import ( + OperationEnvelopeChangeCalendarStartResponse, +) +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + graph_id: str, + *, + body: ChangeCalendarStartRequest, + 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/change-calendar-start".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 | OperationEnvelopeChangeCalendarStartResponse | None: + if response.status_code == 200: + response_200 = OperationEnvelopeChangeCalendarStartResponse.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 | OperationEnvelopeChangeCalendarStartResponse]: + 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: ChangeCalendarStartRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelopeChangeCalendarStartResponse]: + """Change Calendar Start + + Move where an entity's fiscal calendar starts, allowed only before its first close. Earlier adds + open months back to `first_open_period`, for history that predates the start; later removes empty + leading months. Refused once any month has closed (409), and when moving later would drop months + that hold entries or unposted source lines (409). + + **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 (ChangeCalendarStartRequest): Move where an entity's calendar starts, before its + first close. + + Earlier adds open months back to `first_open_period`, for history that + predates the start (a bank feed's backfill, a cutover moved earlier). + Later removes empty leading months. Refused once any month has closed, + and when moving later would drop months that hold entries or unposted + source lines. + + 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 | OperationEnvelopeChangeCalendarStartResponse] + """ + + 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: ChangeCalendarStartRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelopeChangeCalendarStartResponse | None: + """Change Calendar Start + + Move where an entity's fiscal calendar starts, allowed only before its first close. Earlier adds + open months back to `first_open_period`, for history that predates the start; later removes empty + leading months. Refused once any month has closed (409), and when moving later would drop months + that hold entries or unposted source lines (409). + + **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 (ChangeCalendarStartRequest): Move where an entity's calendar starts, before its + first close. + + Earlier adds open months back to `first_open_period`, for history that + predates the start (a bank feed's backfill, a cutover moved earlier). + Later removes empty leading months. Refused once any month has closed, + and when moving later would drop months that hold entries or unposted + source lines. + + 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 | OperationEnvelopeChangeCalendarStartResponse + """ + + 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: ChangeCalendarStartRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelopeChangeCalendarStartResponse]: + """Change Calendar Start + + Move where an entity's fiscal calendar starts, allowed only before its first close. Earlier adds + open months back to `first_open_period`, for history that predates the start; later removes empty + leading months. Refused once any month has closed (409), and when moving later would drop months + that hold entries or unposted source lines (409). + + **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 (ChangeCalendarStartRequest): Move where an entity's calendar starts, before its + first close. + + Earlier adds open months back to `first_open_period`, for history that + predates the start (a bank feed's backfill, a cutover moved earlier). + Later removes empty leading months. Refused once any month has closed, + and when moving later would drop months that hold entries or unposted + source lines. + + 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 | OperationEnvelopeChangeCalendarStartResponse] + """ + + 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: ChangeCalendarStartRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelopeChangeCalendarStartResponse | None: + """Change Calendar Start + + Move where an entity's fiscal calendar starts, allowed only before its first close. Earlier adds + open months back to `first_open_period`, for history that predates the start; later removes empty + leading months. Refused once any month has closed (409), and when moving later would drop months + that hold entries or unposted source lines (409). + + **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 (ChangeCalendarStartRequest): Move where an entity's calendar starts, before its + first close. + + Earlier adds open months back to `first_open_period`, for history that + predates the start (a bank feed's backfill, a cutover moved earlier). + Later removes empty leading months. Refused once any month has closed, + and when moving later would drop months that hold entries or unposted + source lines. + + 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 | OperationEnvelopeChangeCalendarStartResponse + """ + + return ( + await asyncio_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ) + ).parsed diff --git a/robosystems_client/models/__init__.py b/robosystems_client/models/__init__.py index 6d04cb08..57be5a7a 100644 --- a/robosystems_client/models/__init__.py +++ b/robosystems_client/models/__init__.py @@ -47,6 +47,8 @@ from .cancel_subscription_request import CancelSubscriptionRequest from .ceremony_options_response import CeremonyOptionsResponse from .ceremony_options_response_options import CeremonyOptionsResponseOptions +from .change_calendar_start_request import ChangeCalendarStartRequest +from .change_calendar_start_response import ChangeCalendarStartResponse from .change_reporting_style_request import ChangeReportingStyleRequest from .change_reporting_style_response import ChangeReportingStyleResponse from .change_tier_op import ChangeTierOp @@ -71,6 +73,7 @@ from .close_period_response_statement_rule_summary_type_0 import ( ClosePeriodResponseStatementRuleSummaryType0, ) +from .complete_document_upload_op import CompleteDocumentUploadOp from .compute_forecast_request import ComputeForecastRequest from .compute_forecast_response import ComputeForecastResponse from .compute_metrics_request import ComputeMetricsRequest @@ -94,6 +97,8 @@ from .create_checkout_request_resource_config import CreateCheckoutRequestResourceConfig from .create_connection_request import CreateConnectionRequest from .create_connection_request_provider import CreateConnectionRequestProvider +from .create_document_upload_op import CreateDocumentUploadOp +from .create_document_upload_op_content_type import CreateDocumentUploadOpContentType from .create_entity_request import CreateEntityRequest from .create_event_block_request import CreateEventBlockRequest from .create_event_block_request_event_action_type_0 import ( @@ -215,6 +220,9 @@ DisclosuresResponseDisclosuresType0Item, ) from .document_detail_response import DocumentDetailResponse +from .document_evidence import DocumentEvidence +from .document_file_download_response import DocumentFileDownloadResponse +from .document_file_info import DocumentFileInfo from .document_limits import DocumentLimits from .document_list_item import DocumentListItem from .document_list_response import DocumentListResponse @@ -474,6 +482,12 @@ from .operation_envelope_blocked_source_graph_response_status import ( OperationEnvelopeBlockedSourceGraphResponseStatus, ) +from .operation_envelope_change_calendar_start_response import ( + OperationEnvelopeChangeCalendarStartResponse, +) +from .operation_envelope_change_calendar_start_response_status import ( + OperationEnvelopeChangeCalendarStartResponseStatus, +) from .operation_envelope_change_reporting_style_response import ( OperationEnvelopeChangeReportingStyleResponse, ) @@ -1156,6 +1170,8 @@ "CancelSubscriptionRequest", "CeremonyOptionsResponse", "CeremonyOptionsResponseOptions", + "ChangeCalendarStartRequest", + "ChangeCalendarStartResponse", "ChangeReportingStyleRequest", "ChangeReportingStyleResponse", "ChangeTierOp", @@ -1172,6 +1188,7 @@ "ClosePeriodResponseRuleSummaryType0", "ClosePeriodResponseStampedStatementSets", "ClosePeriodResponseStatementRuleSummaryType0", + "CompleteDocumentUploadOp", "ComputedMetricLite", "ComputeForecastRequest", "ComputeForecastResponse", @@ -1195,6 +1212,8 @@ "CreateCheckoutRequestResourceConfig", "CreateConnectionRequest", "CreateConnectionRequestProvider", + "CreateDocumentUploadOp", + "CreateDocumentUploadOpContentType", "CreateEntityRequest", "CreateEventBlockRequest", "CreateEventBlockRequestEventActionType0", @@ -1290,6 +1309,9 @@ "DisclosuresResponseBlocksType0Item", "DisclosuresResponseDisclosuresType0Item", "DocumentDetailResponse", + "DocumentEvidence", + "DocumentFileDownloadResponse", + "DocumentFileInfo", "DocumentLimits", "DocumentListItem", "DocumentListResponse", @@ -1477,6 +1499,8 @@ "OperationEnvelopeBlockedSourceGraphResponseStatus", "OperationEnvelopeBlockSourceGraphResult", "OperationEnvelopeBlockSourceGraphResultStatus", + "OperationEnvelopeChangeCalendarStartResponse", + "OperationEnvelopeChangeCalendarStartResponseStatus", "OperationEnvelopeChangeReportingStyleResponse", "OperationEnvelopeChangeReportingStyleResponseStatus", "OperationEnvelopeClosePeriodResponse", diff --git a/robosystems_client/models/change_calendar_start_request.py b/robosystems_client/models/change_calendar_start_request.py new file mode 100644 index 00000000..86863f76 --- /dev/null +++ b/robosystems_client/models/change_calendar_start_request.py @@ -0,0 +1,111 @@ +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="ChangeCalendarStartRequest") + + +@_attrs_define +class ChangeCalendarStartRequest: + """Move where an entity's calendar starts, before its first close. + + Earlier adds open months back to `first_open_period`, for history that + predates the start (a bank feed's backfill, a cutover moved earlier). + Later removes empty leading months. Refused once any month has closed, + and when moving later would drop months that hold entries or unposted + source lines. + + Attributes: + first_open_period (str): YYYY-MM: the new first month of the calendar, open. + entity_id (None | str | Unset): The entity whose books this acts on, by id. Omit for the group parent — the + single-entity default. + note (None | str | Unset): Free-form note attached to the audit event + """ + + first_open_period: str + entity_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]: + first_open_period = self.first_open_period + + entity_id: None | str | Unset + if isinstance(self.entity_id, Unset): + entity_id = UNSET + else: + entity_id = self.entity_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( + { + "first_open_period": first_open_period, + } + ) + if entity_id is not UNSET: + field_dict["entity_id"] = entity_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: + d = dict(src_dict) + first_open_period = d.pop("first_open_period") + + def _parse_entity_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + entity_id = _parse_entity_id(d.pop("entity_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)) + + change_calendar_start_request = cls( + first_open_period=first_open_period, + entity_id=entity_id, + note=note, + ) + + change_calendar_start_request.additional_properties = d + return change_calendar_start_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/change_calendar_start_response.py b/robosystems_client/models/change_calendar_start_response.py new file mode 100644 index 00000000..a1950054 --- /dev/null +++ b/robosystems_client/models/change_calendar_start_response.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.fiscal_calendar_response import FiscalCalendarResponse + + +T = TypeVar("T", bound="ChangeCalendarStartResponse") + + +@_attrs_define +class ChangeCalendarStartResponse: + """ + Attributes: + fiscal_calendar (FiscalCalendarResponse): Current fiscal calendar state for one entity of a graph. + periods_created (int | Unset): Open FiscalPeriod rows added by moving the start earlier Default: 0. + periods_removed (int | Unset): Empty FiscalPeriod rows removed by moving the start later Default: 0. + """ + + fiscal_calendar: FiscalCalendarResponse + periods_created: int | Unset = 0 + periods_removed: int | Unset = 0 + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + fiscal_calendar = self.fiscal_calendar.to_dict() + + periods_created = self.periods_created + + periods_removed = self.periods_removed + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "fiscal_calendar": fiscal_calendar, + } + ) + if periods_created is not UNSET: + field_dict["periods_created"] = periods_created + if periods_removed is not UNSET: + field_dict["periods_removed"] = periods_removed + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.fiscal_calendar_response import FiscalCalendarResponse + + d = dict(src_dict) + fiscal_calendar = FiscalCalendarResponse.from_dict(d.pop("fiscal_calendar")) + + periods_created = d.pop("periods_created", UNSET) + + periods_removed = d.pop("periods_removed", UNSET) + + change_calendar_start_response = cls( + fiscal_calendar=fiscal_calendar, + periods_created=periods_created, + periods_removed=periods_removed, + ) + + change_calendar_start_response.additional_properties = d + return change_calendar_start_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/complete_document_upload_op.py b/robosystems_client/models/complete_document_upload_op.py new file mode 100644 index 00000000..b9679b69 --- /dev/null +++ b/robosystems_client/models/complete_document_upload_op.py @@ -0,0 +1,123 @@ +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="CompleteDocumentUploadOp") + + +@_attrs_define +class CompleteDocumentUploadOp: + """Body for complete-document-upload: store the uploaded file as a document. + + Attributes: + upload_id (str): The upload create-document-upload returned. + title (str): Document title + tags (list[str] | None | Unset): Optional labels + folder (None | str | Unset): Optional folder + """ + + upload_id: str + title: str + tags: list[str] | None | Unset = UNSET + folder: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + upload_id = self.upload_id + + title = self.title + + tags: list[str] | None | Unset + if isinstance(self.tags, Unset): + tags = UNSET + elif isinstance(self.tags, list): + tags = self.tags + + else: + tags = self.tags + + folder: None | str | Unset + if isinstance(self.folder, Unset): + folder = UNSET + else: + folder = self.folder + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "upload_id": upload_id, + "title": title, + } + ) + if tags is not UNSET: + field_dict["tags"] = tags + if folder is not UNSET: + field_dict["folder"] = folder + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + upload_id = d.pop("upload_id") + + title = d.pop("title") + + def _parse_tags(data: object) -> list[str] | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, list): + raise TypeError() + tags_type_0 = cast(list[str], data) + + return tags_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(list[str] | None | Unset, data) + + tags = _parse_tags(d.pop("tags", UNSET)) + + def _parse_folder(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + folder = _parse_folder(d.pop("folder", UNSET)) + + complete_document_upload_op = cls( + upload_id=upload_id, + title=title, + tags=tags, + folder=folder, + ) + + complete_document_upload_op.additional_properties = d + return complete_document_upload_op + + @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/create_document_upload_op.py b/robosystems_client/models/create_document_upload_op.py new file mode 100644 index 00000000..e95297f0 --- /dev/null +++ b/robosystems_client/models/create_document_upload_op.py @@ -0,0 +1,107 @@ +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 ..models.create_document_upload_op_content_type import ( + CreateDocumentUploadOpContentType, +) +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CreateDocumentUploadOp") + + +@_attrs_define +class CreateDocumentUploadOp: + """Body for create-document-upload: where to upload a document's file. + + Attributes: + file_name (str): The file's name, ending in its type's extension (`.pdf`, `.png`, `.jpg` or `.jpeg`). + content_type (CreateDocumentUploadOpContentType | Unset): The file's media type. Default: + CreateDocumentUploadOpContentType.APPLICATIONPDF. + file_size_bytes (int | None | Unset): The file's exact size in bytes, at most 25 MB. When given it is signed + into the upload URL, so an upload of any other size fails. Completing the upload checks the size either way. + """ + + file_name: str + content_type: CreateDocumentUploadOpContentType | Unset = ( + CreateDocumentUploadOpContentType.APPLICATIONPDF + ) + file_size_bytes: int | None | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + file_name = self.file_name + + content_type: str | Unset = UNSET + if not isinstance(self.content_type, Unset): + content_type = self.content_type.value + + file_size_bytes: int | None | Unset + if isinstance(self.file_size_bytes, Unset): + file_size_bytes = UNSET + else: + file_size_bytes = self.file_size_bytes + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "file_name": file_name, + } + ) + if content_type is not UNSET: + field_dict["content_type"] = content_type + if file_size_bytes is not UNSET: + field_dict["file_size_bytes"] = file_size_bytes + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + file_name = d.pop("file_name") + + _content_type = d.pop("content_type", UNSET) + content_type: CreateDocumentUploadOpContentType | Unset + if isinstance(_content_type, Unset): + content_type = UNSET + else: + content_type = CreateDocumentUploadOpContentType(_content_type) + + def _parse_file_size_bytes(data: object) -> int | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(int | None | Unset, data) + + file_size_bytes = _parse_file_size_bytes(d.pop("file_size_bytes", UNSET)) + + create_document_upload_op = cls( + file_name=file_name, + content_type=content_type, + file_size_bytes=file_size_bytes, + ) + + create_document_upload_op.additional_properties = d + return create_document_upload_op + + @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/create_document_upload_op_content_type.py b/robosystems_client/models/create_document_upload_op_content_type.py new file mode 100644 index 00000000..265f35c3 --- /dev/null +++ b/robosystems_client/models/create_document_upload_op_content_type.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class CreateDocumentUploadOpContentType(str, Enum): + APPLICATIONPDF = "application/pdf" + IMAGEJPEG = "image/jpeg" + IMAGEPNG = "image/png" + + def __str__(self) -> str: + return str(self.value) diff --git a/robosystems_client/models/create_event_block_request.py b/robosystems_client/models/create_event_block_request.py index 95ddb30b..d23aec35 100644 --- a/robosystems_client/models/create_event_block_request.py +++ b/robosystems_client/models/create_event_block_request.py @@ -76,6 +76,9 @@ class CreateEventBlockRequest: this one (e.g. depreciation entries point at the asset_acquired event). discharges_event_id (None | str | Unset): Settlement link: the obligation this event discharges (e.g. cash_received pointing at the originating sale_invoiced). + document_id (None | str | Unset): The stored document this event rests on, as evidence: the invoice PDF, the + vendor bill, the receipt photo (upload it with create-document-upload and complete-document-upload). Must be a + document on this graph. While the event is live, the document cannot be deleted. apply_handlers (bool | Unset): When True, resolves the event_type to a handler (Python registry first, then DSL) and fires it atomically with event creation. Default: False. """ @@ -102,6 +105,7 @@ class CreateEventBlockRequest: dimension_ids: list[str] | Unset = UNSET obligated_by_event_id: None | str | Unset = UNSET discharges_event_id: None | str | Unset = UNSET + document_id: None | str | Unset = UNSET apply_handlers: bool | Unset = False additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -206,6 +210,12 @@ def to_dict(self) -> dict[str, Any]: else: discharges_event_id = self.discharges_event_id + document_id: None | str | Unset + if isinstance(self.document_id, Unset): + document_id = UNSET + else: + document_id = self.document_id + apply_handlers = self.apply_handlers field_dict: dict[str, Any] = {} @@ -250,6 +260,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["obligated_by_event_id"] = obligated_by_event_id if discharges_event_id is not UNSET: field_dict["discharges_event_id"] = discharges_event_id + if document_id is not UNSET: + field_dict["document_id"] = document_id if apply_handlers is not UNSET: field_dict["apply_handlers"] = apply_handlers @@ -430,6 +442,15 @@ def _parse_discharges_event_id(data: object) -> None | str | Unset: d.pop("discharges_event_id", UNSET) ) + def _parse_document_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + document_id = _parse_document_id(d.pop("document_id", UNSET)) + apply_handlers = d.pop("apply_handlers", UNSET) create_event_block_request = cls( @@ -453,6 +474,7 @@ def _parse_discharges_event_id(data: object) -> None | str | Unset: dimension_ids=dimension_ids, obligated_by_event_id=obligated_by_event_id, discharges_event_id=discharges_event_id, + document_id=document_id, apply_handlers=apply_handlers, ) diff --git a/robosystems_client/models/document_detail_response.py b/robosystems_client/models/document_detail_response.py index 3fbf44f5..a0a80e31 100644 --- a/robosystems_client/models/document_detail_response.py +++ b/robosystems_client/models/document_detail_response.py @@ -1,13 +1,18 @@ from __future__ import annotations from collections.abc import Mapping -from typing import Any, TypeVar, cast +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.document_evidence import DocumentEvidence + from ..models.document_file_info import DocumentFileInfo + + T = TypeVar("T", bound="DocumentDetailResponse") @@ -29,6 +34,11 @@ class DocumentDetailResponse: folder (None | str | Unset): external_id (None | str | Unset): source_provider (None | str | Unset): + file (DocumentFileInfo | None | Unset): The stored file, for a document that is one; its `content` is empty. + Download it from `GET /documents/{document_id}/file`. + evidence_for (list[DocumentEvidence] | Unset): The live events that name this document as their evidence (an + invoice, a bill, a statement balance), newest first, at most 50. While any are listed, the document cannot be + deleted. """ id: str @@ -44,9 +54,13 @@ class DocumentDetailResponse: folder: None | str | Unset = UNSET external_id: None | str | Unset = UNSET source_provider: None | str | Unset = UNSET + file: DocumentFileInfo | None | Unset = UNSET + evidence_for: list[DocumentEvidence] | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: + from ..models.document_file_info import DocumentFileInfo + id = self.id graph_id = self.graph_id @@ -92,6 +106,21 @@ def to_dict(self) -> dict[str, Any]: else: source_provider = self.source_provider + file: dict[str, Any] | None | Unset + if isinstance(self.file, Unset): + file = UNSET + elif isinstance(self.file, DocumentFileInfo): + file = self.file.to_dict() + else: + file = self.file + + evidence_for: list[dict[str, Any]] | Unset = UNSET + if not isinstance(self.evidence_for, Unset): + evidence_for = [] + for evidence_for_item_data in self.evidence_for: + evidence_for_item = evidence_for_item_data.to_dict() + evidence_for.append(evidence_for_item) + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -115,11 +144,18 @@ def to_dict(self) -> dict[str, Any]: field_dict["external_id"] = external_id if source_provider is not UNSET: field_dict["source_provider"] = source_provider + if file is not UNSET: + field_dict["file"] = file + if evidence_for is not UNSET: + field_dict["evidence_for"] = evidence_for return field_dict @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.document_evidence import DocumentEvidence + from ..models.document_file_info import DocumentFileInfo + d = dict(src_dict) id = d.pop("id") @@ -183,6 +219,32 @@ def _parse_source_provider(data: object) -> None | str | Unset: source_provider = _parse_source_provider(d.pop("source_provider", UNSET)) + def _parse_file(data: object) -> DocumentFileInfo | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + file_type_0 = DocumentFileInfo.from_dict(data) + + return file_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(DocumentFileInfo | None | Unset, data) + + file = _parse_file(d.pop("file", UNSET)) + + _evidence_for = d.pop("evidence_for", UNSET) + evidence_for: list[DocumentEvidence] | Unset = UNSET + if _evidence_for is not UNSET: + evidence_for = [] + for evidence_for_item_data in _evidence_for: + evidence_for_item = DocumentEvidence.from_dict(evidence_for_item_data) + + evidence_for.append(evidence_for_item) + document_detail_response = cls( id=id, graph_id=graph_id, @@ -197,6 +259,8 @@ def _parse_source_provider(data: object) -> None | str | Unset: folder=folder, external_id=external_id, source_provider=source_provider, + file=file, + evidence_for=evidence_for, ) document_detail_response.additional_properties = d diff --git a/robosystems_client/models/document_evidence.py b/robosystems_client/models/document_evidence.py new file mode 100644 index 00000000..1360151e --- /dev/null +++ b/robosystems_client/models/document_evidence.py @@ -0,0 +1,86 @@ +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="DocumentEvidence") + + +@_attrs_define +class DocumentEvidence: + """A live event on the books that names the document as its evidence. + + Attributes: + event_id (str): + event_type (str): + status (str): + occurred_at (str): + """ + + event_id: str + event_type: str + status: str + occurred_at: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + event_id = self.event_id + + event_type = self.event_type + + status = self.status + + occurred_at = self.occurred_at + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "event_id": event_id, + "event_type": event_type, + "status": status, + "occurred_at": occurred_at, + } + ) + + 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") + + event_type = d.pop("event_type") + + status = d.pop("status") + + occurred_at = d.pop("occurred_at") + + document_evidence = cls( + event_id=event_id, + event_type=event_type, + status=status, + occurred_at=occurred_at, + ) + + document_evidence.additional_properties = d + return document_evidence + + @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/document_file_download_response.py b/robosystems_client/models/document_file_download_response.py new file mode 100644 index 00000000..87dcf26e --- /dev/null +++ b/robosystems_client/models/document_file_download_response.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +if TYPE_CHECKING: + from ..models.document_file_info import DocumentFileInfo + + +T = TypeVar("T", bound="DocumentFileDownloadResponse") + + +@_attrs_define +class DocumentFileDownloadResponse: + """A short-lived link to a stored document file. + + Attributes: + document_id (str): + download_url (str): Presigned URL to GET the file. + expires_in (int): Seconds until the URL expires. + file (DocumentFileInfo): The stored file behind a document, when it is one. + """ + + document_id: str + download_url: str + expires_in: int + file: DocumentFileInfo + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + document_id = self.document_id + + download_url = self.download_url + + expires_in = self.expires_in + + file = self.file.to_dict() + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "document_id": document_id, + "download_url": download_url, + "expires_in": expires_in, + "file": file, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.document_file_info import DocumentFileInfo + + d = dict(src_dict) + document_id = d.pop("document_id") + + download_url = d.pop("download_url") + + expires_in = d.pop("expires_in") + + file = DocumentFileInfo.from_dict(d.pop("file")) + + document_file_download_response = cls( + document_id=document_id, + download_url=download_url, + expires_in=expires_in, + file=file, + ) + + document_file_download_response.additional_properties = d + return document_file_download_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/document_file_info.py b/robosystems_client/models/document_file_info.py new file mode 100644 index 00000000..24f0dc37 --- /dev/null +++ b/robosystems_client/models/document_file_info.py @@ -0,0 +1,87 @@ +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="DocumentFileInfo") + + +@_attrs_define +class DocumentFileInfo: + """The stored file behind a document, when it is one. + + Attributes: + file_name (str): The file's name as uploaded. + content_type (str): The file's media type. + size_bytes (int): The stored file's size. + sha256 (str): Hex SHA-256 of the stored bytes, taken when the upload completed. A stored file never changes, so + this identifies it. + """ + + file_name: str + content_type: str + size_bytes: int + sha256: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + file_name = self.file_name + + content_type = self.content_type + + size_bytes = self.size_bytes + + sha256 = self.sha256 + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "file_name": file_name, + "content_type": content_type, + "size_bytes": size_bytes, + "sha256": sha256, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + file_name = d.pop("file_name") + + content_type = d.pop("content_type") + + size_bytes = d.pop("size_bytes") + + sha256 = d.pop("sha256") + + document_file_info = cls( + file_name=file_name, + content_type=content_type, + size_bytes=size_bytes, + sha256=sha256, + ) + + document_file_info.additional_properties = d + return document_file_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/document_list_item.py b/robosystems_client/models/document_list_item.py index 1fe7a1b3..ef7984fe 100644 --- a/robosystems_client/models/document_list_item.py +++ b/robosystems_client/models/document_list_item.py @@ -1,13 +1,17 @@ from __future__ import annotations from collections.abc import Mapping -from typing import Any, TypeVar, cast +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.document_file_info import DocumentFileInfo + + T = TypeVar("T", bound="DocumentListItem") @@ -24,6 +28,7 @@ class DocumentListItem: updated_at (str): folder (None | str | Unset): tags (list[str] | None | Unset): + file (DocumentFileInfo | None | Unset): The stored file, for a document that is one. """ id: str @@ -34,9 +39,12 @@ class DocumentListItem: updated_at: str folder: None | str | Unset = UNSET tags: list[str] | None | Unset = UNSET + file: DocumentFileInfo | None | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: + from ..models.document_file_info import DocumentFileInfo + id = self.id document_title = self.document_title @@ -64,6 +72,14 @@ def to_dict(self) -> dict[str, Any]: else: tags = self.tags + file: dict[str, Any] | None | Unset + if isinstance(self.file, Unset): + file = UNSET + elif isinstance(self.file, DocumentFileInfo): + file = self.file.to_dict() + else: + file = self.file + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -80,11 +96,15 @@ def to_dict(self) -> dict[str, Any]: field_dict["folder"] = folder if tags is not UNSET: field_dict["tags"] = tags + if file is not UNSET: + field_dict["file"] = file return field_dict @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.document_file_info import DocumentFileInfo + d = dict(src_dict) id = d.pop("id") @@ -124,6 +144,23 @@ def _parse_tags(data: object) -> list[str] | None | Unset: tags = _parse_tags(d.pop("tags", UNSET)) + def _parse_file(data: object) -> DocumentFileInfo | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + file_type_0 = DocumentFileInfo.from_dict(data) + + return file_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(DocumentFileInfo | None | Unset, data) + + file = _parse_file(d.pop("file", UNSET)) + document_list_item = cls( id=id, document_title=document_title, @@ -133,6 +170,7 @@ def _parse_tags(data: object) -> list[str] | None | Unset: updated_at=updated_at, folder=folder, tags=tags, + file=file, ) document_list_item.additional_properties = d diff --git a/robosystems_client/models/event_block_envelope.py b/robosystems_client/models/event_block_envelope.py index 91986c6c..06ca36ae 100644 --- a/robosystems_client/models/event_block_envelope.py +++ b/robosystems_client/models/event_block_envelope.py @@ -80,6 +80,8 @@ class EventBlockEnvelope: this one (e.g. depreciation entries point at the originating `asset_acquired` event). discharges_event_id (None | str | Unset): Settlement link — the obligation this event discharges (e.g. `cash_received` pointing at the originating `sale_invoiced`). + document_id (None | str | Unset): The stored document the event rests on (an invoice, a bill, a receipt, a + statement); read it with get-document. """ id: str @@ -108,6 +110,7 @@ class EventBlockEnvelope: replaces_event_id: None | str | Unset = UNSET obligated_by_event_id: None | str | Unset = UNSET discharges_event_id: None | str | Unset = UNSET + document_id: None | str | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -219,6 +222,12 @@ def to_dict(self) -> dict[str, Any]: else: discharges_event_id = self.discharges_event_id + document_id: None | str | Unset + if isinstance(self.document_id, Unset): + document_id = UNSET + else: + document_id = self.document_id + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -265,6 +274,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["obligated_by_event_id"] = obligated_by_event_id if discharges_event_id is not UNSET: field_dict["discharges_event_id"] = discharges_event_id + if document_id is not UNSET: + field_dict["document_id"] = document_id return field_dict @@ -442,6 +453,15 @@ def _parse_discharges_event_id(data: object) -> None | str | Unset: d.pop("discharges_event_id", UNSET) ) + def _parse_document_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + document_id = _parse_document_id(d.pop("document_id", UNSET)) + event_block_envelope = cls( id=id, event_type=event_type, @@ -469,6 +489,7 @@ def _parse_discharges_event_id(data: object) -> None | str | Unset: replaces_event_id=replaces_event_id, obligated_by_event_id=obligated_by_event_id, discharges_event_id=discharges_event_id, + document_id=document_id, ) event_block_envelope.additional_properties = d diff --git a/robosystems_client/models/mercury_connection_config.py b/robosystems_client/models/mercury_connection_config.py index db7f2fa8..b4bf107e 100644 --- a/robosystems_client/models/mercury_connection_config.py +++ b/robosystems_client/models/mercury_connection_config.py @@ -26,8 +26,8 @@ class MercuryConnectionConfig: hosted product refuses it. Attributes: - since_date (datetime.date | None | Unset): First day of the backfill (ISO 8601). Defaults to 1 January of last - year. Incremental syncs never look back before it. + since_date (datetime.date | None | Unset): First day of the backfill (ISO 8601). Defaults to two years back. + Incremental syncs never look back before it, and lines dated in a closed period are never captured. include_treasury (bool | Unset): Capture treasury-account activity alongside checking/savings. Default: True. api_key (None | str | Unset): A personal read-only Mercury API token, for deployments that allow the api_key credential mode. Omit to connect over OAuth. diff --git a/robosystems_client/models/operation_envelope_change_calendar_start_response.py b/robosystems_client/models/operation_envelope_change_calendar_start_response.py new file mode 100644 index 00000000..cd616f9a --- /dev/null +++ b/robosystems_client/models/operation_envelope_change_calendar_start_response.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_change_calendar_start_response_status import ( + OperationEnvelopeChangeCalendarStartResponseStatus, +) +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.change_calendar_start_response import ChangeCalendarStartResponse + + +T = TypeVar("T", bound="OperationEnvelopeChangeCalendarStartResponse") + + +@_attrs_define +class OperationEnvelopeChangeCalendarStartResponse: + """ + Attributes: + operation (str): Kebab-case operation name + operation_id (str): op_-prefixed ULID for audit and SSE correlation + status (OperationEnvelopeChangeCalendarStartResponseStatus): Operation lifecycle state + at (str): ISO-8601 UTC timestamp + result (ChangeCalendarStartResponse | None | Unset): Command-specific result payload + created_by (None | str | Unset): User ID that initiated the operation + 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: OperationEnvelopeChangeCalendarStartResponseStatus + at: str + result: ChangeCalendarStartResponse | None | 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.change_calendar_start_response import ChangeCalendarStartResponse + + 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, ChangeCalendarStartResponse): + 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.change_calendar_start_response import ChangeCalendarStartResponse + + d = dict(src_dict) + operation = d.pop("operation") + + operation_id = d.pop("operationId") + + status = OperationEnvelopeChangeCalendarStartResponseStatus(d.pop("status")) + + at = d.pop("at") + + def _parse_result(data: object) -> ChangeCalendarStartResponse | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + result_type_0 = ChangeCalendarStartResponse.from_dict(data) + + return result_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(ChangeCalendarStartResponse | None | 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_change_calendar_start_response = cls( + operation=operation, + operation_id=operation_id, + status=status, + at=at, + result=result, + created_by=created_by, + idempotent_replay=idempotent_replay, + ) + + operation_envelope_change_calendar_start_response.additional_properties = d + return operation_envelope_change_calendar_start_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_change_calendar_start_response_status.py b/robosystems_client/models/operation_envelope_change_calendar_start_response_status.py new file mode 100644 index 00000000..24786ffa --- /dev/null +++ b/robosystems_client/models/operation_envelope_change_calendar_start_response_status.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class OperationEnvelopeChangeCalendarStartResponseStatus(str, Enum): + COMPLETED = "completed" + FAILED = "failed" + PENDING = "pending" + + def __str__(self) -> str: + return str(self.value) diff --git a/robosystems_client/models/plaid_connection_config.py b/robosystems_client/models/plaid_connection_config.py index 09c0791f..1ebb8024 100644 --- a/robosystems_client/models/plaid_connection_config.py +++ b/robosystems_client/models/plaid_connection_config.py @@ -26,7 +26,8 @@ class PlaidConnectionConfig: Attributes: since_date (datetime.date | None | Unset): First day of the backfill (ISO 8601), and how much history Plaid is - asked to pull for the new Item (at most two years). Defaults to 1 January of last year. + asked to pull for the new Item (at most two years). Defaults to as far back as Plaid goes. Lines dated in a + closed period are never captured, whatever the window. entity_id (None | str | Unset): The entity whose books the feed's accounts land on: a subsidiary's id, or omit for the group parent. Required when QuickBooks keeps the parent's books. Each account can be moved to another entity later with `link-bank-account`. diff --git a/robosystems_client/models/preview_reconciliations_request.py b/robosystems_client/models/preview_reconciliations_request.py index d28202aa..01b7a2ae 100644 --- a/robosystems_client/models/preview_reconciliations_request.py +++ b/robosystems_client/models/preview_reconciliations_request.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Mapping -from typing import Any, TypeVar +from typing import Any, TypeVar, cast from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -27,6 +27,8 @@ class PreviewReconciliationsRequest: PreviewReconciliationsRequestMethod.SOURCE_LEDGER. include_tied (bool | Unset): Also return the accounts that tie. Off by default: the differences are the work, and the counts cover the rest. Default: False. + entity_id (None | str | Unset): The entity whose books to compare, by id. Omit for the group parent. + `source_ledger` applies to the group parent only: QuickBooks keeps its books, not a subsidiary's. """ period: str @@ -34,6 +36,7 @@ class PreviewReconciliationsRequest: PreviewReconciliationsRequestMethod.SOURCE_LEDGER ) include_tied: bool | Unset = False + entity_id: None | str | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -45,6 +48,12 @@ def to_dict(self) -> dict[str, Any]: include_tied = self.include_tied + entity_id: None | str | Unset + if isinstance(self.entity_id, Unset): + entity_id = UNSET + else: + entity_id = self.entity_id + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -56,6 +65,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["method"] = method if include_tied is not UNSET: field_dict["include_tied"] = include_tied + if entity_id is not UNSET: + field_dict["entity_id"] = entity_id return field_dict @@ -73,10 +84,20 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: include_tied = d.pop("include_tied", UNSET) + def _parse_entity_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + entity_id = _parse_entity_id(d.pop("entity_id", UNSET)) + preview_reconciliations_request = cls( period=period, method=method, include_tied=include_tied, + entity_id=entity_id, ) preview_reconciliations_request.additional_properties = d diff --git a/robosystems_client/models/record_statement_balance_request.py b/robosystems_client/models/record_statement_balance_request.py index 865f61fd..16afd1f2 100644 --- a/robosystems_client/models/record_statement_balance_request.py +++ b/robosystems_client/models/record_statement_balance_request.py @@ -22,6 +22,8 @@ class RecordStatementBalanceRequest: balance (float): The ending balance as the statement shows it, as a positive number in the account's normal direction: money in a bank account, or the amount owed on a loan or a card. Negative for the opposite, such as an overdrawn bank account. + entity_id (None | str | Unset): The entity whose books the account is in, by id. Omit for the group parent. The + account must be in that entity's chart. document_id (None | str | Unset): The statement itself, as a document already added with create-document. Kept on the record as evidence. note (None | str | Unset): Anything worth keeping with the recorded balance. @@ -30,6 +32,7 @@ class RecordStatementBalanceRequest: element_id: str as_of: datetime.date balance: float + entity_id: None | str | Unset = UNSET document_id: None | str | Unset = UNSET note: None | str | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -41,6 +44,12 @@ def to_dict(self) -> dict[str, Any]: balance = self.balance + entity_id: None | str | Unset + if isinstance(self.entity_id, Unset): + entity_id = UNSET + else: + entity_id = self.entity_id + document_id: None | str | Unset if isinstance(self.document_id, Unset): document_id = UNSET @@ -62,6 +71,8 @@ def to_dict(self) -> dict[str, Any]: "balance": balance, } ) + if entity_id is not UNSET: + field_dict["entity_id"] = entity_id if document_id is not UNSET: field_dict["document_id"] = document_id if note is not UNSET: @@ -78,6 +89,15 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: balance = d.pop("balance") + def _parse_entity_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + entity_id = _parse_entity_id(d.pop("entity_id", UNSET)) + def _parse_document_id(data: object) -> None | str | Unset: if data is None: return data @@ -100,6 +120,7 @@ def _parse_note(data: object) -> None | str | Unset: element_id=element_id, as_of=as_of, balance=balance, + entity_id=entity_id, document_id=document_id, note=note, ) diff --git a/robosystems_client/models/refresh_reconciliations_request.py b/robosystems_client/models/refresh_reconciliations_request.py index 2e27275c..614af2cc 100644 --- a/robosystems_client/models/refresh_reconciliations_request.py +++ b/robosystems_client/models/refresh_reconciliations_request.py @@ -1,11 +1,13 @@ from __future__ import annotations from collections.abc import Mapping -from typing import Any, TypeVar +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="RefreshReconciliationsRequest") @@ -15,14 +17,23 @@ class RefreshReconciliationsRequest: Attributes: period (str): Period to reconcile at its last day, as YYYY-MM. + entity_id (None | str | Unset): The entity whose books to reconcile, by id. Omit for the group parent. Each + entity's reconciliations, and the close they hold, are its own. """ period: str + entity_id: None | str | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: period = self.period + entity_id: None | str | Unset + if isinstance(self.entity_id, Unset): + entity_id = UNSET + else: + entity_id = self.entity_id + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -30,6 +41,8 @@ def to_dict(self) -> dict[str, Any]: "period": period, } ) + if entity_id is not UNSET: + field_dict["entity_id"] = entity_id return field_dict @@ -38,8 +51,18 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) period = d.pop("period") + def _parse_entity_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + entity_id = _parse_entity_id(d.pop("entity_id", UNSET)) + refresh_reconciliations_request = cls( period=period, + entity_id=entity_id, ) refresh_reconciliations_request.additional_properties = d diff --git a/robosystems_client/models/update_event_block_request.py b/robosystems_client/models/update_event_block_request.py index b96ca0a3..6cb3e8e5 100644 --- a/robosystems_client/models/update_event_block_request.py +++ b/robosystems_client/models/update_event_block_request.py @@ -54,6 +54,8 @@ class UpdateEventBlockRequest: reclassifying after the fact. obligated_by_event_id (None | str | Unset): Set/update the forward-materialization link. discharges_event_id (None | str | Unset): Set/update the settlement link. + document_id (None | str | Unset): Set or replace the stored document the event rests on; an empty string + detaches it. Must be a document on this graph. Unset = unchanged. """ event_id: str @@ -65,6 +67,7 @@ class UpdateEventBlockRequest: event_action: None | Unset | UpdateEventBlockRequestEventActionType0 = UNSET obligated_by_event_id: None | str | Unset = UNSET discharges_event_id: None | str | Unset = UNSET + document_id: None | str | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -122,6 +125,12 @@ def to_dict(self) -> dict[str, Any]: else: discharges_event_id = self.discharges_event_id + document_id: None | str | Unset + if isinstance(self.document_id, Unset): + document_id = UNSET + else: + document_id = self.document_id + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -145,6 +154,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["obligated_by_event_id"] = obligated_by_event_id if discharges_event_id is not UNSET: field_dict["discharges_event_id"] = discharges_event_id + if document_id is not UNSET: + field_dict["document_id"] = document_id return field_dict @@ -259,6 +270,15 @@ def _parse_discharges_event_id(data: object) -> None | str | Unset: d.pop("discharges_event_id", UNSET) ) + def _parse_document_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + document_id = _parse_document_id(d.pop("document_id", UNSET)) + update_event_block_request = cls( event_id=event_id, transition_to=transition_to, @@ -269,6 +289,7 @@ def _parse_discharges_event_id(data: object) -> None | str | Unset: event_action=event_action, obligated_by_event_id=obligated_by_event_id, discharges_event_id=discharges_event_id, + document_id=document_id, ) update_event_block_request.additional_properties = d