diff --git a/robosystems_client/api/extensions_robo_ledger/op_create_schedule.py b/robosystems_client/api/extensions_robo_ledger/op_create_information_block.py similarity index 62% rename from robosystems_client/api/extensions_robo_ledger/op_create_schedule.py rename to robosystems_client/api/extensions_robo_ledger/op_create_information_block.py index b175311..e0620b2 100644 --- a/robosystems_client/api/extensions_robo_ledger/op_create_schedule.py +++ b/robosystems_client/api/extensions_robo_ledger/op_create_information_block.py @@ -6,7 +6,7 @@ from ... import errors from ...client import AuthenticatedClient, Client -from ...models.create_schedule_request import CreateScheduleRequest +from ...models.create_information_block_request import CreateInformationBlockRequest from ...models.http_validation_error import HTTPValidationError from ...models.operation_envelope import OperationEnvelope from ...models.operation_error import OperationError @@ -16,7 +16,7 @@ def _get_kwargs( graph_id: str, *, - body: CreateScheduleRequest, + body: CreateInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} @@ -25,7 +25,7 @@ def _get_kwargs( _kwargs: dict[str, Any] = { "method": "post", - "url": "/extensions/roboledger/{graph_id}/operations/create-schedule".format( + "url": "/extensions/roboledger/{graph_id}/operations/create-information-block".format( graph_id=quote(str(graph_id), safe=""), ), } @@ -103,13 +103,14 @@ def sync_detailed( graph_id: str, *, client: AuthenticatedClient, - body: CreateScheduleRequest, + body: CreateInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Response[Any | HTTPValidationError | OperationEnvelope | OperationError]: - """Create Schedule + """Create Information Block - Create a schedule and pre-generate monthly amortization facts spanning the period range. - `entry_template` defines the debit/credit elements used by `create-closing-entry` each period. + Generic Information Block construction entry. `block_type` selects the registered block type; + `payload` is validated against that type's creation schema at dispatch. Schedule dispatches to the + existing Schedule machinery; statement block types raise 501 (use create-report instead). **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. @@ -117,7 +118,15 @@ def sync_detailed( Args: graph_id (str): idempotency_key (None | str | Unset): - body (CreateScheduleRequest): + body (CreateInformationBlockRequest): Generic create request — discriminator + typed-at- + dispatch payload. + + ``block_type`` selects the registry entry. ``payload`` is validated + against ``BlockTypeRegistryEntry.create_request_model`` (e.g. + :class:`CreateScheduleRequest` for ``block_type='schedule'``) by the + command dispatcher. Chosen over a Pydantic discriminated union on the + top-level request so adding a block type is one registry line, not a + union-arm edit at the request-model layer. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -144,13 +153,14 @@ def sync( graph_id: str, *, client: AuthenticatedClient, - body: CreateScheduleRequest, + body: CreateInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Any | HTTPValidationError | OperationEnvelope | OperationError | None: - """Create Schedule + """Create Information Block - Create a schedule and pre-generate monthly amortization facts spanning the period range. - `entry_template` defines the debit/credit elements used by `create-closing-entry` each period. + Generic Information Block construction entry. `block_type` selects the registered block type; + `payload` is validated against that type's creation schema at dispatch. Schedule dispatches to the + existing Schedule machinery; statement block types raise 501 (use create-report instead). **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. @@ -158,7 +168,15 @@ def sync( Args: graph_id (str): idempotency_key (None | str | Unset): - body (CreateScheduleRequest): + body (CreateInformationBlockRequest): Generic create request — discriminator + typed-at- + dispatch payload. + + ``block_type`` selects the registry entry. ``payload`` is validated + against ``BlockTypeRegistryEntry.create_request_model`` (e.g. + :class:`CreateScheduleRequest` for ``block_type='schedule'``) by the + command dispatcher. Chosen over a Pydantic discriminated union on the + top-level request so adding a block type is one registry line, not a + union-arm edit at the request-model layer. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -180,13 +198,14 @@ async def asyncio_detailed( graph_id: str, *, client: AuthenticatedClient, - body: CreateScheduleRequest, + body: CreateInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Response[Any | HTTPValidationError | OperationEnvelope | OperationError]: - """Create Schedule + """Create Information Block - Create a schedule and pre-generate monthly amortization facts spanning the period range. - `entry_template` defines the debit/credit elements used by `create-closing-entry` each period. + Generic Information Block construction entry. `block_type` selects the registered block type; + `payload` is validated against that type's creation schema at dispatch. Schedule dispatches to the + existing Schedule machinery; statement block types raise 501 (use create-report instead). **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. @@ -194,7 +213,15 @@ async def asyncio_detailed( Args: graph_id (str): idempotency_key (None | str | Unset): - body (CreateScheduleRequest): + body (CreateInformationBlockRequest): Generic create request — discriminator + typed-at- + dispatch payload. + + ``block_type`` selects the registry entry. ``payload`` is validated + against ``BlockTypeRegistryEntry.create_request_model`` (e.g. + :class:`CreateScheduleRequest` for ``block_type='schedule'``) by the + command dispatcher. Chosen over a Pydantic discriminated union on the + top-level request so adding a block type is one registry line, not a + union-arm edit at the request-model layer. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -219,13 +246,14 @@ async def asyncio( graph_id: str, *, client: AuthenticatedClient, - body: CreateScheduleRequest, + body: CreateInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Any | HTTPValidationError | OperationEnvelope | OperationError | None: - """Create Schedule + """Create Information Block - Create a schedule and pre-generate monthly amortization facts spanning the period range. - `entry_template` defines the debit/credit elements used by `create-closing-entry` each period. + Generic Information Block construction entry. `block_type` selects the registered block type; + `payload` is validated against that type's creation schema at dispatch. Schedule dispatches to the + existing Schedule machinery; statement block types raise 501 (use create-report instead). **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. @@ -233,7 +261,15 @@ async def asyncio( Args: graph_id (str): idempotency_key (None | str | Unset): - body (CreateScheduleRequest): + body (CreateInformationBlockRequest): Generic create request — discriminator + typed-at- + dispatch payload. + + ``block_type`` selects the registry entry. ``payload`` is validated + against ``BlockTypeRegistryEntry.create_request_model`` (e.g. + :class:`CreateScheduleRequest` for ``block_type='schedule'``) by the + command dispatcher. Chosen over a Pydantic discriminated union on the + top-level request so adding a block type is one registry line, not a + union-arm edit at the request-model layer. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. diff --git a/robosystems_client/api/extensions_robo_ledger/op_delete_schedule.py b/robosystems_client/api/extensions_robo_ledger/op_delete_information_block.py similarity index 72% rename from robosystems_client/api/extensions_robo_ledger/op_delete_schedule.py rename to robosystems_client/api/extensions_robo_ledger/op_delete_information_block.py index d3924ec..8ca0bc3 100644 --- a/robosystems_client/api/extensions_robo_ledger/op_delete_schedule.py +++ b/robosystems_client/api/extensions_robo_ledger/op_delete_information_block.py @@ -6,7 +6,7 @@ from ... import errors from ...client import AuthenticatedClient, Client -from ...models.delete_schedule_request import DeleteScheduleRequest +from ...models.delete_information_block_request import DeleteInformationBlockRequest from ...models.http_validation_error import HTTPValidationError from ...models.operation_envelope import OperationEnvelope from ...models.operation_error import OperationError @@ -16,7 +16,7 @@ def _get_kwargs( graph_id: str, *, - body: DeleteScheduleRequest, + body: DeleteInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} @@ -25,7 +25,7 @@ def _get_kwargs( _kwargs: dict[str, Any] = { "method": "post", - "url": "/extensions/roboledger/{graph_id}/operations/delete-schedule".format( + "url": "/extensions/roboledger/{graph_id}/operations/delete-information-block".format( graph_id=quote(str(graph_id), safe=""), ), } @@ -103,13 +103,14 @@ def sync_detailed( graph_id: str, *, client: AuthenticatedClient, - body: DeleteScheduleRequest, + body: DeleteInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Response[Any | HTTPValidationError | OperationEnvelope | OperationError]: - """Delete Schedule + """Delete Information Block - Permanently delete a schedule, cascading through facts and associations. For ending a schedule early - without removing history, use truncate-schedule instead. + Generic Information Block deletion entry. Returns a thin confirmation (deleted / structure_id / + block_type / name). Block types whose Structures are library-seeded cannot be deleted per tenant and + surface 501. **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. @@ -117,12 +118,11 @@ def sync_detailed( Args: graph_id (str): idempotency_key (None | str | Unset): - body (DeleteScheduleRequest): Delete a schedule — cascades through facts and associations. + body (DeleteInformationBlockRequest): Generic delete request — mirrors + :class:`CreateInformationBlockRequest`. - Hard deletes the Structure, all Facts tied to it, and all - Associations tied to it. This is a permanent, irreversible - operation. For ending a schedule early without removing history, - use truncate-schedule instead. + Validated against the registry entry's ``delete_request_model``. + Block types that don't support deletion raise ``NotImplementedError``. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -149,13 +149,14 @@ def sync( graph_id: str, *, client: AuthenticatedClient, - body: DeleteScheduleRequest, + body: DeleteInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Any | HTTPValidationError | OperationEnvelope | OperationError | None: - """Delete Schedule + """Delete Information Block - Permanently delete a schedule, cascading through facts and associations. For ending a schedule early - without removing history, use truncate-schedule instead. + Generic Information Block deletion entry. Returns a thin confirmation (deleted / structure_id / + block_type / name). Block types whose Structures are library-seeded cannot be deleted per tenant and + surface 501. **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. @@ -163,12 +164,11 @@ def sync( Args: graph_id (str): idempotency_key (None | str | Unset): - body (DeleteScheduleRequest): Delete a schedule — cascades through facts and associations. + body (DeleteInformationBlockRequest): Generic delete request — mirrors + :class:`CreateInformationBlockRequest`. - Hard deletes the Structure, all Facts tied to it, and all - Associations tied to it. This is a permanent, irreversible - operation. For ending a schedule early without removing history, - use truncate-schedule instead. + Validated against the registry entry's ``delete_request_model``. + Block types that don't support deletion raise ``NotImplementedError``. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -190,13 +190,14 @@ async def asyncio_detailed( graph_id: str, *, client: AuthenticatedClient, - body: DeleteScheduleRequest, + body: DeleteInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Response[Any | HTTPValidationError | OperationEnvelope | OperationError]: - """Delete Schedule + """Delete Information Block - Permanently delete a schedule, cascading through facts and associations. For ending a schedule early - without removing history, use truncate-schedule instead. + Generic Information Block deletion entry. Returns a thin confirmation (deleted / structure_id / + block_type / name). Block types whose Structures are library-seeded cannot be deleted per tenant and + surface 501. **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. @@ -204,12 +205,11 @@ async def asyncio_detailed( Args: graph_id (str): idempotency_key (None | str | Unset): - body (DeleteScheduleRequest): Delete a schedule — cascades through facts and associations. + body (DeleteInformationBlockRequest): Generic delete request — mirrors + :class:`CreateInformationBlockRequest`. - Hard deletes the Structure, all Facts tied to it, and all - Associations tied to it. This is a permanent, irreversible - operation. For ending a schedule early without removing history, - use truncate-schedule instead. + Validated against the registry entry's ``delete_request_model``. + Block types that don't support deletion raise ``NotImplementedError``. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -234,13 +234,14 @@ async def asyncio( graph_id: str, *, client: AuthenticatedClient, - body: DeleteScheduleRequest, + body: DeleteInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Any | HTTPValidationError | OperationEnvelope | OperationError | None: - """Delete Schedule + """Delete Information Block - Permanently delete a schedule, cascading through facts and associations. For ending a schedule early - without removing history, use truncate-schedule instead. + Generic Information Block deletion entry. Returns a thin confirmation (deleted / structure_id / + block_type / name). Block types whose Structures are library-seeded cannot be deleted per tenant and + surface 501. **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. @@ -248,12 +249,11 @@ async def asyncio( Args: graph_id (str): idempotency_key (None | str | Unset): - body (DeleteScheduleRequest): Delete a schedule — cascades through facts and associations. + body (DeleteInformationBlockRequest): Generic delete request — mirrors + :class:`CreateInformationBlockRequest`. - Hard deletes the Structure, all Facts tied to it, and all - Associations tied to it. This is a permanent, irreversible - operation. For ending a schedule early without removing history, - use truncate-schedule instead. + Validated against the registry entry's ``delete_request_model``. + Block types that don't support deletion raise ``NotImplementedError``. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. diff --git a/robosystems_client/api/extensions_robo_ledger/op_evaluate_rules.py b/robosystems_client/api/extensions_robo_ledger/op_evaluate_rules.py new file mode 100644 index 0000000..b8e375a --- /dev/null +++ b/robosystems_client/api/extensions_robo_ledger/op_evaluate_rules.py @@ -0,0 +1,301 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.evaluate_rules_request import EvaluateRulesRequest +from ...models.http_validation_error import HTTPValidationError +from ...models.operation_envelope import OperationEnvelope +from ...models.operation_error import OperationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + graph_id: str, + *, + body: EvaluateRulesRequest, + 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/evaluate-rules".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 +) -> Any | HTTPValidationError | OperationEnvelope | OperationError | None: + if response.status_code == 200: + response_200 = OperationEnvelope.from_dict(response.json()) + + return response_200 + + if response.status_code == 400: + response_400 = OperationError.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = cast(Any, None) + return response_401 + + if response.status_code == 403: + response_403 = cast(Any, None) + return response_403 + + if response.status_code == 404: + response_404 = OperationError.from_dict(response.json()) + + return response_404 + + if response.status_code == 409: + response_409 = OperationError.from_dict(response.json()) + + return response_409 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if response.status_code == 429: + response_429 = cast(Any, None) + return response_429 + + if response.status_code == 500: + response_500 = cast(Any, None) + 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[Any | HTTPValidationError | OperationEnvelope | OperationError]: + 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: EvaluateRulesRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[Any | HTTPValidationError | OperationEnvelope | OperationError]: + """Evaluate Rules for an Information Block + + Runs every rule targeting the given structure (plus element- and association-scoped rules for the + structure's atoms), binds $Variable references to in-scope facts via qname lookup, writes one + VerificationResult row per rule, and returns the results plus a status-keyed summary. Phase delta.3 + — decoding mode, 5 patterns (EqualTo, RollUp, RollForward, Exists, CoExists). + + **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 (EvaluateRulesRequest): Request body for the ``evaluate-rules`` operation (Phase + delta.3). + + Runs every rule scoped to ``structure_id`` (plus element/association- + scoped rules for the structure's atoms), binds ``$Variable`` references + to facts via qname lookup, and writes one + :class:`VerificationResult` row per rule. + + Optional ``period_start`` / ``period_end`` narrow the fact-binding + window; without them the engine uses the most recent ``in_scope`` fact + for each element regardless of period. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any | HTTPValidationError | OperationEnvelope | OperationError] + """ + + 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: EvaluateRulesRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Any | HTTPValidationError | OperationEnvelope | OperationError | None: + """Evaluate Rules for an Information Block + + Runs every rule targeting the given structure (plus element- and association-scoped rules for the + structure's atoms), binds $Variable references to in-scope facts via qname lookup, writes one + VerificationResult row per rule, and returns the results plus a status-keyed summary. Phase delta.3 + — decoding mode, 5 patterns (EqualTo, RollUp, RollForward, Exists, CoExists). + + **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 (EvaluateRulesRequest): Request body for the ``evaluate-rules`` operation (Phase + delta.3). + + Runs every rule scoped to ``structure_id`` (plus element/association- + scoped rules for the structure's atoms), binds ``$Variable`` references + to facts via qname lookup, and writes one + :class:`VerificationResult` row per rule. + + Optional ``period_start`` / ``period_end`` narrow the fact-binding + window; without them the engine uses the most recent ``in_scope`` fact + for each element regardless of period. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Any | HTTPValidationError | OperationEnvelope | OperationError + """ + + 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: EvaluateRulesRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[Any | HTTPValidationError | OperationEnvelope | OperationError]: + """Evaluate Rules for an Information Block + + Runs every rule targeting the given structure (plus element- and association-scoped rules for the + structure's atoms), binds $Variable references to in-scope facts via qname lookup, writes one + VerificationResult row per rule, and returns the results plus a status-keyed summary. Phase delta.3 + — decoding mode, 5 patterns (EqualTo, RollUp, RollForward, Exists, CoExists). + + **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 (EvaluateRulesRequest): Request body for the ``evaluate-rules`` operation (Phase + delta.3). + + Runs every rule scoped to ``structure_id`` (plus element/association- + scoped rules for the structure's atoms), binds ``$Variable`` references + to facts via qname lookup, and writes one + :class:`VerificationResult` row per rule. + + Optional ``period_start`` / ``period_end`` narrow the fact-binding + window; without them the engine uses the most recent ``in_scope`` fact + for each element regardless of period. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any | HTTPValidationError | OperationEnvelope | OperationError] + """ + + 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: EvaluateRulesRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Any | HTTPValidationError | OperationEnvelope | OperationError | None: + """Evaluate Rules for an Information Block + + Runs every rule targeting the given structure (plus element- and association-scoped rules for the + structure's atoms), binds $Variable references to in-scope facts via qname lookup, writes one + VerificationResult row per rule, and returns the results plus a status-keyed summary. Phase delta.3 + — decoding mode, 5 patterns (EqualTo, RollUp, RollForward, Exists, CoExists). + + **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 (EvaluateRulesRequest): Request body for the ``evaluate-rules`` operation (Phase + delta.3). + + Runs every rule scoped to ``structure_id`` (plus element/association- + scoped rules for the structure's atoms), binds ``$Variable`` references + to facts via qname lookup, and writes one + :class:`VerificationResult` row per rule. + + Optional ``period_start`` / ``period_end`` narrow the fact-binding + window; without them the engine uses the most recent ``in_scope`` fact + for each element regardless of period. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Any | HTTPValidationError | OperationEnvelope | OperationError + """ + + return ( + await asyncio_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ) + ).parsed diff --git a/robosystems_client/api/extensions_robo_ledger/op_update_schedule.py b/robosystems_client/api/extensions_robo_ledger/op_update_information_block.py similarity index 68% rename from robosystems_client/api/extensions_robo_ledger/op_update_schedule.py rename to robosystems_client/api/extensions_robo_ledger/op_update_information_block.py index bce925a..c871dca 100644 --- a/robosystems_client/api/extensions_robo_ledger/op_update_schedule.py +++ b/robosystems_client/api/extensions_robo_ledger/op_update_information_block.py @@ -9,14 +9,14 @@ from ...models.http_validation_error import HTTPValidationError from ...models.operation_envelope import OperationEnvelope from ...models.operation_error import OperationError -from ...models.update_schedule_request import UpdateScheduleRequest +from ...models.update_information_block_request import UpdateInformationBlockRequest from ...types import UNSET, Response, Unset def _get_kwargs( graph_id: str, *, - body: UpdateScheduleRequest, + body: UpdateInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} @@ -25,7 +25,7 @@ def _get_kwargs( _kwargs: dict[str, Any] = { "method": "post", - "url": "/extensions/roboledger/{graph_id}/operations/update-schedule".format( + "url": "/extensions/roboledger/{graph_id}/operations/update-information-block".format( graph_id=quote(str(graph_id), safe=""), ), } @@ -103,13 +103,14 @@ def sync_detailed( graph_id: str, *, client: AuthenticatedClient, - body: UpdateScheduleRequest, + body: UpdateInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Response[Any | HTTPValidationError | OperationEnvelope | OperationError]: - """Update Schedule + """Update Information Block - Update mutable fields on a schedule: name, entry_template, schedule_metadata. Period range and - monthly_amount are NOT editable — use truncate-schedule + create-schedule instead. + Generic Information Block update entry. Dispatches by `block_type` to the registered mutation + handler. Block types whose Structures are library-seeded and immutable (statement family) surface + 501. **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. @@ -117,16 +118,13 @@ def sync_detailed( Args: graph_id (str): idempotency_key (None | str | Unset): - body (UpdateScheduleRequest): Update mutable fields on a schedule. + body (UpdateInformationBlockRequest): Generic update request — mirrors + :class:`CreateInformationBlockRequest`. - Editable: name, entry_template, schedule_metadata (all live on the - Structure row / its metadata_ JSONB column). - - NOT editable via this op: period_start, period_end, monthly_amount. - Those require fact regeneration — use truncate-schedule (end early) - and create-schedule (start new) instead. - - Omitted fields are left unchanged. + Validated against the registry entry's ``update_request_model``. + Block types that don't support updates (e.g. the statement family, + whose Structures are library-seeded) surface ``NotImplementedError`` + from their dispatch handler, which the registrar maps to HTTP 501. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -153,13 +151,14 @@ def sync( graph_id: str, *, client: AuthenticatedClient, - body: UpdateScheduleRequest, + body: UpdateInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Any | HTTPValidationError | OperationEnvelope | OperationError | None: - """Update Schedule + """Update Information Block - Update mutable fields on a schedule: name, entry_template, schedule_metadata. Period range and - monthly_amount are NOT editable — use truncate-schedule + create-schedule instead. + Generic Information Block update entry. Dispatches by `block_type` to the registered mutation + handler. Block types whose Structures are library-seeded and immutable (statement family) surface + 501. **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. @@ -167,16 +166,13 @@ def sync( Args: graph_id (str): idempotency_key (None | str | Unset): - body (UpdateScheduleRequest): Update mutable fields on a schedule. - - Editable: name, entry_template, schedule_metadata (all live on the - Structure row / its metadata_ JSONB column). - - NOT editable via this op: period_start, period_end, monthly_amount. - Those require fact regeneration — use truncate-schedule (end early) - and create-schedule (start new) instead. + body (UpdateInformationBlockRequest): Generic update request — mirrors + :class:`CreateInformationBlockRequest`. - Omitted fields are left unchanged. + Validated against the registry entry's ``update_request_model``. + Block types that don't support updates (e.g. the statement family, + whose Structures are library-seeded) surface ``NotImplementedError`` + from their dispatch handler, which the registrar maps to HTTP 501. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -198,13 +194,14 @@ async def asyncio_detailed( graph_id: str, *, client: AuthenticatedClient, - body: UpdateScheduleRequest, + body: UpdateInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Response[Any | HTTPValidationError | OperationEnvelope | OperationError]: - """Update Schedule + """Update Information Block - Update mutable fields on a schedule: name, entry_template, schedule_metadata. Period range and - monthly_amount are NOT editable — use truncate-schedule + create-schedule instead. + Generic Information Block update entry. Dispatches by `block_type` to the registered mutation + handler. Block types whose Structures are library-seeded and immutable (statement family) surface + 501. **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,16 +209,13 @@ async def asyncio_detailed( Args: graph_id (str): idempotency_key (None | str | Unset): - body (UpdateScheduleRequest): Update mutable fields on a schedule. + body (UpdateInformationBlockRequest): Generic update request — mirrors + :class:`CreateInformationBlockRequest`. - Editable: name, entry_template, schedule_metadata (all live on the - Structure row / its metadata_ JSONB column). - - NOT editable via this op: period_start, period_end, monthly_amount. - Those require fact regeneration — use truncate-schedule (end early) - and create-schedule (start new) instead. - - Omitted fields are left unchanged. + Validated against the registry entry's ``update_request_model``. + Block types that don't support updates (e.g. the statement family, + whose Structures are library-seeded) surface ``NotImplementedError`` + from their dispatch handler, which the registrar maps to HTTP 501. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -246,13 +240,14 @@ async def asyncio( graph_id: str, *, client: AuthenticatedClient, - body: UpdateScheduleRequest, + body: UpdateInformationBlockRequest, idempotency_key: None | str | Unset = UNSET, ) -> Any | HTTPValidationError | OperationEnvelope | OperationError | None: - """Update Schedule + """Update Information Block - Update mutable fields on a schedule: name, entry_template, schedule_metadata. Period range and - monthly_amount are NOT editable — use truncate-schedule + create-schedule instead. + Generic Information Block update entry. Dispatches by `block_type` to the registered mutation + handler. Block types whose Structures are library-seeded and immutable (statement family) surface + 501. **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. @@ -260,16 +255,13 @@ async def asyncio( Args: graph_id (str): idempotency_key (None | str | Unset): - body (UpdateScheduleRequest): Update mutable fields on a schedule. - - Editable: name, entry_template, schedule_metadata (all live on the - Structure row / its metadata_ JSONB column). - - NOT editable via this op: period_start, period_end, monthly_amount. - Those require fact regeneration — use truncate-schedule (end early) - and create-schedule (start new) instead. + body (UpdateInformationBlockRequest): Generic update request — mirrors + :class:`CreateInformationBlockRequest`. - Omitted fields are left unchanged. + Validated against the registry entry's ``update_request_model``. + Block types that don't support updates (e.g. the statement family, + whose Structures are library-seeded) surface ``NotImplementedError`` + from their dispatch handler, which the registrar maps to HTTP 501. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. diff --git a/robosystems_client/clients/ledger_client.py b/robosystems_client/clients/ledger_client.py index 9f70476..6ae793d 100644 --- a/robosystems_client/clients/ledger_client.py +++ b/robosystems_client/clients/ledger_client.py @@ -44,8 +44,8 @@ from ..api.extensions_robo_ledger.op_create_mapping_association import ( sync_detailed as op_create_mapping_association, ) -from ..api.extensions_robo_ledger.op_create_schedule import ( - sync_detailed as op_create_schedule, +from ..api.extensions_robo_ledger.op_create_information_block import ( + sync_detailed as op_create_information_block, ) from ..api.extensions_robo_ledger.op_create_structure import ( sync_detailed as op_create_structure, @@ -77,8 +77,8 @@ from ..api.extensions_robo_ledger.op_update_entity import ( sync_detailed as op_update_entity, ) -from ..api.extensions_robo_ledger.op_update_schedule import ( - sync_detailed as op_update_schedule, +from ..api.extensions_robo_ledger.op_update_information_block import ( + sync_detailed as op_update_information_block, ) from ..api.extensions_robo_ledger.op_update_structure import ( sync_detailed as op_update_structure, @@ -137,8 +137,8 @@ from ..api.extensions_robo_ledger.op_delete_journal_entry import ( sync_detailed as op_delete_journal_entry, ) -from ..api.extensions_robo_ledger.op_delete_schedule import ( - sync_detailed as op_delete_schedule, +from ..api.extensions_robo_ledger.op_delete_information_block import ( + sync_detailed as op_delete_information_block, ) from ..api.extensions_robo_ledger.op_delete_structure import ( sync_detailed as op_delete_structure, @@ -165,16 +165,16 @@ GET_MAPPING_QUERY, GET_PERIOD_CLOSE_STATUS_QUERY, GET_PERIOD_DRAFTS_QUERY, + GET_INFORMATION_BLOCK_QUERY, GET_REPORTING_TAXONOMY_QUERY, - GET_SCHEDULE_FACTS_QUERY, GET_SUMMARY_QUERY, GET_TRANSACTION_QUERY, GET_TRIAL_BALANCE_QUERY, LIST_ACCOUNTS_QUERY, LIST_ELEMENTS_QUERY, LIST_ENTITIES_QUERY, + LIST_INFORMATION_BLOCKS_QUERY, LIST_MAPPINGS_QUERY, - LIST_SCHEDULES_QUERY, LIST_STRUCTURES_QUERY, LIST_TAXONOMIES_QUERY, LIST_TRANSACTIONS_QUERY, @@ -187,6 +187,8 @@ parse_entities, parse_entity, parse_fiscal_calendar, + parse_information_block, + parse_information_blocks, parse_mapped_trial_balance, parse_mapping, parse_mapping_coverage, @@ -194,8 +196,6 @@ parse_period_close_status, parse_period_drafts, parse_reporting_taxonomy, - parse_schedule_facts, - parse_schedules, parse_structures, parse_summary, parse_taxonomies, @@ -226,7 +226,10 @@ from ..models.delete_association_request import DeleteAssociationRequest from ..models.delete_element_request import DeleteElementRequest from ..models.delete_journal_entry_request import DeleteJournalEntryRequest -from ..models.delete_schedule_request import DeleteScheduleRequest +from ..models.delete_information_block_request import DeleteInformationBlockRequest +from ..models.delete_information_block_request_payload import ( + DeleteInformationBlockRequestPayload, +) from ..models.delete_structure_request import DeleteStructureRequest from ..models.delete_taxonomy_request import DeleteTaxonomyRequest from ..models.link_entity_taxonomy_request import LinkEntityTaxonomyRequest @@ -234,7 +237,10 @@ from ..models.update_association_request import UpdateAssociationRequest from ..models.update_element_request import UpdateElementRequest from ..models.update_journal_entry_request import UpdateJournalEntryRequest -from ..models.update_schedule_request import UpdateScheduleRequest +from ..models.update_information_block_request import UpdateInformationBlockRequest +from ..models.update_information_block_request_payload import ( + UpdateInformationBlockRequestPayload, +) from ..models.update_structure_request import UpdateStructureRequest from ..models.update_taxonomy_request import UpdateTaxonomyRequest from ..models.close_period_operation import ClosePeriodOperation @@ -247,7 +253,10 @@ from ..models.create_mapping_association_operation import ( CreateMappingAssociationOperation, ) -from ..models.create_schedule_request import CreateScheduleRequest +from ..models.create_information_block_request import CreateInformationBlockRequest +from ..models.create_information_block_request_payload import ( + CreateInformationBlockRequestPayload, +) from ..models.create_structure_request import CreateStructureRequest from ..models.create_structure_request_structure_type import ( CreateStructureRequestStructureType, @@ -844,34 +853,52 @@ def delete_association(self, graph_id: str, association_id: str) -> dict[str, An envelope = self._call_op("Delete association", response) return envelope.result if envelope.result is not None else {"deleted": True} - # ── Schedules ────────────────────────────────────────────────────── + # ── Information Blocks ───────────────────────────────────────────── + + def get_information_block( + self, + graph_id: str, + block_id: str, + ) -> dict[str, Any] | None: + """Fetch an Information Block envelope by id — the cross-block-type read. - def list_schedules(self, graph_id: str) -> list[dict[str, Any]]: - """List all schedule structures with metadata.""" - data = self._query(graph_id, LIST_SCHEDULES_QUERY) - return parse_schedules(data) + Returns ``None`` when the block doesn't exist or its type isn't + registered. See ``information-block.md`` for the envelope contract. + """ + data = self._query( + graph_id, + GET_INFORMATION_BLOCK_QUERY, + {"id": block_id}, + ) + return parse_information_block(data) - def get_schedule_facts( + def list_information_blocks( self, graph_id: str, - structure_id: str, - period_start: str | None = None, - period_end: str | None = None, + *, + block_type: str | None = None, + category: str | None = None, + limit: int | None = None, + offset: int | None = None, ) -> list[dict[str, Any]]: - """Schedule facts optionally filtered by period window.""" + """List Information Block envelopes, optionally filtered. + + Replaces the old ``list_schedules`` method — use + ``block_type='schedule'`` for the same set of blocks. + """ data = self._query( graph_id, - GET_SCHEDULE_FACTS_QUERY, + LIST_INFORMATION_BLOCKS_QUERY, { - "structureId": structure_id, - "periodStart": period_start, - "periodEnd": period_end, + "blockType": block_type, + "category": category, + "limit": limit, + "offset": offset, }, ) - parsed = parse_schedule_facts(data) - if parsed is None: - return [] - return parsed.get("facts", []) + return parse_information_blocks(data) + + # ── Schedules ────────────────────────────────────────────────────── def create_schedule( self, @@ -895,7 +922,7 @@ def create_schedule( auto_reverse: bool = False, ) -> dict[str, Any]: """Create a new schedule with pre-generated monthly facts.""" - body_dict: dict[str, Any] = { + payload_dict: dict[str, Any] = { "name": name, "element_ids": element_ids, "period_start": period_start, @@ -910,7 +937,7 @@ def create_schedule( }, } if taxonomy_id: - body_dict["taxonomy_id"] = taxonomy_id + payload_dict["taxonomy_id"] = taxonomy_id schedule_metadata: dict[str, Any] = {} if method: schedule_metadata["method"] = method @@ -923,10 +950,11 @@ def create_schedule( if asset_element_id: schedule_metadata["asset_element_id"] = asset_element_id if schedule_metadata: - body_dict["schedule_metadata"] = schedule_metadata + payload_dict["schedule_metadata"] = schedule_metadata - body = CreateScheduleRequest.from_dict(body_dict) - response = op_create_schedule( + payload = CreateInformationBlockRequestPayload.from_dict(payload_dict) + body = CreateInformationBlockRequest(block_type="schedule", payload=payload) + response = op_create_information_block( graph_id=graph_id, body=body, client=self._get_client() ) envelope = self._call_op("Create schedule", response) @@ -951,10 +979,15 @@ def truncate_schedule( envelope = self._call_op("Truncate schedule", response) return envelope.result or {} - def update_schedule(self, graph_id: str, body: dict[str, Any]) -> dict[str, Any]: + def update_schedule( + self, graph_id: str, structure_id: str, body: dict[str, Any] + ) -> dict[str, Any]: """Update mutable fields on a schedule (name, entry_template, metadata).""" - request = UpdateScheduleRequest.from_dict(body) - response = op_update_schedule( + payload = UpdateInformationBlockRequestPayload.from_dict( + {"structure_id": structure_id, **body} + ) + request = UpdateInformationBlockRequest(block_type="schedule", payload=payload) + response = op_update_information_block( graph_id=graph_id, body=request, client=self._get_client() ) envelope = self._call_op("Update schedule", response) @@ -962,8 +995,11 @@ def update_schedule(self, graph_id: str, body: dict[str, Any]) -> dict[str, Any] def delete_schedule(self, graph_id: str, structure_id: str) -> dict[str, Any]: """Permanently delete a schedule (cascades through facts + associations).""" - body = DeleteScheduleRequest(structure_id=structure_id) - response = op_delete_schedule( + payload = DeleteInformationBlockRequestPayload.from_dict( + {"structure_id": structure_id} + ) + body = DeleteInformationBlockRequest(block_type="schedule", payload=payload) + response = op_delete_information_block( graph_id=graph_id, body=body, client=self._get_client() ) envelope = self._call_op("Delete schedule", response) diff --git a/robosystems_client/graphql/queries/ledger/__init__.py b/robosystems_client/graphql/queries/ledger/__init__.py index 53480ef..1216b56 100644 --- a/robosystems_client/graphql/queries/ledger/__init__.py +++ b/robosystems_client/graphql/queries/ledger/__init__.py @@ -494,46 +494,74 @@ def parse_mapping_coverage(data: dict[str, Any]) -> dict[str, Any] | None: return keys_to_snake(c) if c is not None else None -# ── Schedules ────────────────────────────────────────────────────────── - -LIST_SCHEDULES_QUERY = """ -query ListLedgerSchedules { - schedules { - schedules { - structureId name taxonomyName entryTemplate scheduleMetadata - totalPeriods periodsWithEntries +# ── Information Blocks ───────────────────────────────────────────────── + +GET_INFORMATION_BLOCK_QUERY = """ +query GetInformationBlock($id: ID!) { + informationBlock(id: $id) { + id blockType name displayName category + taxonomyId taxonomyName + informationModel { conceptArrangement memberArrangement } + artifact { topic parentheticalNote template mechanics } + elements { + id qname name code elementType + isAbstract isMonetary balanceType periodType + } + connections { + id fromElementId toElementId associationType + arcrole orderValue weight + } + facts { + id elementId value periodStart periodEnd + periodType unit factScope factSetId } } } """.strip() -def parse_schedules(data: dict[str, Any]) -> list[dict[str, Any]]: - s = data.get("schedules") or {} - return [keys_to_snake(x) for x in s.get("schedules", [])] +def parse_information_block(data: dict[str, Any]) -> dict[str, Any] | None: + block = data.get("informationBlock") + return keys_to_snake(block) if block is not None else None -GET_SCHEDULE_FACTS_QUERY = """ -query GetLedgerScheduleFacts( - $structureId: String! - $periodStart: Date - $periodEnd: Date +LIST_INFORMATION_BLOCKS_QUERY = """ +query ListInformationBlocks( + $blockType: String + $category: String + $limit: Int + $offset: Int ) { - scheduleFacts( - structureId: $structureId - periodStart: $periodStart - periodEnd: $periodEnd + informationBlocks( + blockType: $blockType + category: $category + limit: $limit + offset: $offset ) { - structureId - facts { elementId elementName value periodStart periodEnd } + id blockType name displayName category + taxonomyId taxonomyName + informationModel { conceptArrangement memberArrangement } + artifact { topic parentheticalNote template mechanics } + elements { + id qname name code elementType + isAbstract isMonetary balanceType periodType + } + connections { + id fromElementId toElementId associationType + arcrole orderValue weight + } + facts { + id elementId value periodStart periodEnd + periodType unit factScope factSetId + } } } """.strip() -def parse_schedule_facts(data: dict[str, Any]) -> dict[str, Any] | None: - f = data.get("scheduleFacts") - return keys_to_snake(f) if f is not None else None +def parse_information_blocks(data: dict[str, Any]) -> list[dict[str, Any]]: + blocks = data.get("informationBlocks") or [] + return [keys_to_snake(b) for b in blocks] # ── Period close ─────────────────────────────────────────────────────── diff --git a/robosystems_client/models/__init__.py b/robosystems_client/models/__init__.py index 41d4406..abfcf72 100644 --- a/robosystems_client/models/__init__.py +++ b/robosystems_client/models/__init__.py @@ -74,6 +74,10 @@ from .create_element_request_period_type import CreateElementRequestPeriodType from .create_element_request_source import CreateElementRequestSource from .create_graph_request import CreateGraphRequest +from .create_information_block_request import CreateInformationBlockRequest +from .create_information_block_request_payload import ( + CreateInformationBlockRequestPayload, +) from .create_journal_entry_request import CreateJournalEntryRequest from .create_journal_entry_request_status import CreateJournalEntryRequestStatus from .create_journal_entry_request_type import CreateJournalEntryRequestType @@ -90,7 +94,6 @@ from .create_publish_list_request import CreatePublishListRequest from .create_report_request import CreateReportRequest from .create_repository_subscription_request import CreateRepositorySubscriptionRequest -from .create_schedule_request import CreateScheduleRequest from .create_security_request import CreateSecurityRequest from .create_security_request_terms import CreateSecurityRequestTerms from .create_structure_request import CreateStructureRequest @@ -120,13 +123,16 @@ from .delete_association_request import DeleteAssociationRequest from .delete_element_request import DeleteElementRequest from .delete_file_response import DeleteFileResponse +from .delete_information_block_request import DeleteInformationBlockRequest +from .delete_information_block_request_payload import ( + DeleteInformationBlockRequestPayload, +) from .delete_journal_entry_request import DeleteJournalEntryRequest from .delete_mapping_association_operation import DeleteMappingAssociationOperation from .delete_portfolio_operation import DeletePortfolioOperation from .delete_position_operation import DeletePositionOperation from .delete_publish_list_operation import DeletePublishListOperation from .delete_report_operation import DeleteReportOperation -from .delete_schedule_request import DeleteScheduleRequest from .delete_security_operation import DeleteSecurityOperation from .delete_structure_request import DeleteStructureRequest from .delete_subgraph_op import DeleteSubgraphOp @@ -150,9 +156,8 @@ EnhancedCreditTransactionResponseMetadata, ) from .enhanced_file_status_layers import EnhancedFileStatusLayers -from .entry_template_request import EntryTemplateRequest -from .entry_template_request_entry_type import EntryTemplateRequestEntryType from .error_response import ErrorResponse +from .evaluate_rules_request import EvaluateRulesRequest from .file_info import FileInfo from .file_layer_status import FileLayerStatus from .file_status_update import FileStatusUpdate @@ -279,7 +284,6 @@ from .response_mode import ResponseMode from .restore_backup_op import RestoreBackupOp from .reverse_journal_entry_request import ReverseJournalEntryRequest -from .schedule_metadata_request import ScheduleMetadataRequest from .schema_export_response import SchemaExportResponse from .schema_export_response_data_stats_type_0 import SchemaExportResponseDataStatsType0 from .schema_export_response_schema_definition_type_0 import ( @@ -345,6 +349,10 @@ ) from .update_entity_request import UpdateEntityRequest from .update_file_response_updatefile import UpdateFileResponseUpdatefile +from .update_information_block_request import UpdateInformationBlockRequest +from .update_information_block_request_payload import ( + UpdateInformationBlockRequestPayload, +) from .update_journal_entry_request import UpdateJournalEntryRequest from .update_journal_entry_request_type_type_0 import UpdateJournalEntryRequestTypeType0 from .update_member_role_request import UpdateMemberRoleRequest @@ -353,7 +361,6 @@ from .update_portfolio_operation import UpdatePortfolioOperation from .update_position_operation import UpdatePositionOperation from .update_publish_list_operation import UpdatePublishListOperation -from .update_schedule_request import UpdateScheduleRequest from .update_security_operation import UpdateSecurityOperation from .update_security_operation_terms_type_0 import UpdateSecurityOperationTermsType0 from .update_structure_request import UpdateStructureRequest @@ -438,6 +445,8 @@ "CreateElementRequestPeriodType", "CreateElementRequestSource", "CreateGraphRequest", + "CreateInformationBlockRequest", + "CreateInformationBlockRequestPayload", "CreateJournalEntryRequest", "CreateJournalEntryRequestStatus", "CreateJournalEntryRequestType", @@ -450,7 +459,6 @@ "CreatePublishListRequest", "CreateReportRequest", "CreateRepositorySubscriptionRequest", - "CreateScheduleRequest", "CreateSecurityRequest", "CreateSecurityRequestTerms", "CreateStructureRequest", @@ -478,13 +486,14 @@ "DeleteAssociationRequest", "DeleteElementRequest", "DeleteFileResponse", + "DeleteInformationBlockRequest", + "DeleteInformationBlockRequestPayload", "DeleteJournalEntryRequest", "DeleteMappingAssociationOperation", "DeletePortfolioOperation", "DeletePositionOperation", "DeletePublishListOperation", "DeleteReportOperation", - "DeleteScheduleRequest", "DeleteSecurityOperation", "DeleteStructureRequest", "DeleteSubgraphOp", @@ -504,9 +513,8 @@ "EnhancedCreditTransactionResponse", "EnhancedCreditTransactionResponseMetadata", "EnhancedFileStatusLayers", - "EntryTemplateRequest", - "EntryTemplateRequestEntryType", "ErrorResponse", + "EvaluateRulesRequest", "FileInfo", "FileLayerStatus", "FileStatusUpdate", @@ -619,7 +627,6 @@ "ResponseMode", "RestoreBackupOp", "ReverseJournalEntryRequest", - "ScheduleMetadataRequest", "SchemaExportResponse", "SchemaExportResponseDataStatsType0", "SchemaExportResponseSchemaDefinitionType0", @@ -671,6 +678,8 @@ "UpdateElementRequestPeriodTypeType0", "UpdateEntityRequest", "UpdateFileResponseUpdatefile", + "UpdateInformationBlockRequest", + "UpdateInformationBlockRequestPayload", "UpdateJournalEntryRequest", "UpdateJournalEntryRequestTypeType0", "UpdateMemberRoleRequest", @@ -679,7 +688,6 @@ "UpdatePortfolioOperation", "UpdatePositionOperation", "UpdatePublishListOperation", - "UpdateScheduleRequest", "UpdateSecurityOperation", "UpdateSecurityOperationTermsType0", "UpdateStructureRequest", diff --git a/robosystems_client/models/create_information_block_request.py b/robosystems_client/models/create_information_block_request.py new file mode 100644 index 0000000..66e4fc7 --- /dev/null +++ b/robosystems_client/models/create_information_block_request.py @@ -0,0 +1,100 @@ +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.create_information_block_request_payload import ( + CreateInformationBlockRequestPayload, + ) + + +T = TypeVar("T", bound="CreateInformationBlockRequest") + + +@_attrs_define +class CreateInformationBlockRequest: + """Generic create request — discriminator + typed-at-dispatch payload. + + ``block_type`` selects the registry entry. ``payload`` is validated + against ``BlockTypeRegistryEntry.create_request_model`` (e.g. + :class:`CreateScheduleRequest` for ``block_type='schedule'``) by the + command dispatcher. Chosen over a Pydantic discriminated union on the + top-level request so adding a block type is one registry line, not a + union-arm edit at the request-model layer. + + Attributes: + block_type (str): Block type discriminator. Must match a registered entry in + robosystems.operations.information_block.registry.REGISTRY. + payload (CreateInformationBlockRequestPayload | Unset): Block-type-specific creation payload. Shape-validated + against the registry entry's `create_request_model` at dispatch time; the validation error surfaces as a 422 at + the API boundary. + """ + + block_type: str + payload: CreateInformationBlockRequestPayload | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + block_type = self.block_type + + payload: dict[str, Any] | Unset = UNSET + if not isinstance(self.payload, Unset): + payload = self.payload.to_dict() + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "block_type": block_type, + } + ) + if payload is not UNSET: + field_dict["payload"] = payload + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.create_information_block_request_payload import ( + CreateInformationBlockRequestPayload, + ) + + d = dict(src_dict) + block_type = d.pop("block_type") + + _payload = d.pop("payload", UNSET) + payload: CreateInformationBlockRequestPayload | Unset + if isinstance(_payload, Unset): + payload = UNSET + else: + payload = CreateInformationBlockRequestPayload.from_dict(_payload) + + create_information_block_request = cls( + block_type=block_type, + payload=payload, + ) + + create_information_block_request.additional_properties = d + return create_information_block_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/delete_schedule_request.py b/robosystems_client/models/create_information_block_request_payload.py similarity index 57% rename from robosystems_client/models/delete_schedule_request.py rename to robosystems_client/models/create_information_block_request_payload.py index ab5a6a4..5774734 100644 --- a/robosystems_client/models/delete_schedule_request.py +++ b/robosystems_client/models/create_information_block_request_payload.py @@ -6,49 +6,32 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field -T = TypeVar("T", bound="DeleteScheduleRequest") +T = TypeVar("T", bound="CreateInformationBlockRequestPayload") @_attrs_define -class DeleteScheduleRequest: - """Delete a schedule — cascades through facts and associations. +class CreateInformationBlockRequestPayload: + """Block-type-specific creation payload. Shape-validated against the registry entry's `create_request_model` at + dispatch time; the validation error surfaces as a 422 at the API boundary. - Hard deletes the Structure, all Facts tied to it, and all - Associations tied to it. This is a permanent, irreversible - operation. For ending a schedule early without removing history, - use truncate-schedule instead. - - Attributes: - structure_id (str): """ - structure_id: str additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: - structure_id = self.structure_id field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) - field_dict.update( - { - "structure_id": structure_id, - } - ) return field_dict @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) - structure_id = d.pop("structure_id") - - delete_schedule_request = cls( - structure_id=structure_id, - ) + create_information_block_request_payload = cls() - delete_schedule_request.additional_properties = d - return delete_schedule_request + create_information_block_request_payload.additional_properties = d + return create_information_block_request_payload @property def additional_keys(self) -> list[str]: diff --git a/robosystems_client/models/create_schedule_request.py b/robosystems_client/models/create_schedule_request.py deleted file mode 100644 index d01324a..0000000 --- a/robosystems_client/models/create_schedule_request.py +++ /dev/null @@ -1,199 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar, cast - -from attrs import define as _attrs_define -from attrs import field as _attrs_field -from dateutil.parser import isoparse - -from ..types import UNSET, Unset - -if TYPE_CHECKING: - from ..models.entry_template_request import EntryTemplateRequest - from ..models.schedule_metadata_request import ScheduleMetadataRequest - - -T = TypeVar("T", bound="CreateScheduleRequest") - - -@_attrs_define -class CreateScheduleRequest: - """ - Attributes: - name (str): Schedule name - element_ids (list[str]): Element IDs to include - period_start (datetime.date): First period start - period_end (datetime.date): Last period end - monthly_amount (int): Monthly amount in cents - entry_template (EntryTemplateRequest): - taxonomy_id (None | str | Unset): Taxonomy ID (auto-creates if omitted) - schedule_metadata (None | ScheduleMetadataRequest | Unset): - closed_through (datetime.date | None | Unset): If provided, facts with period_end ≤ this date are flagged as - 'historical' (already reflected in opening balances, ignored by the close workflow). Used during initial ledger - setup to create schedules whose early facts have already been captured elsewhere. - """ - - name: str - element_ids: list[str] - period_start: datetime.date - period_end: datetime.date - monthly_amount: int - entry_template: EntryTemplateRequest - taxonomy_id: None | str | Unset = UNSET - schedule_metadata: None | ScheduleMetadataRequest | Unset = UNSET - closed_through: datetime.date | None | Unset = UNSET - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - from ..models.schedule_metadata_request import ScheduleMetadataRequest - - name = self.name - - element_ids = self.element_ids - - period_start = self.period_start.isoformat() - - period_end = self.period_end.isoformat() - - monthly_amount = self.monthly_amount - - entry_template = self.entry_template.to_dict() - - taxonomy_id: None | str | Unset - if isinstance(self.taxonomy_id, Unset): - taxonomy_id = UNSET - else: - taxonomy_id = self.taxonomy_id - - schedule_metadata: dict[str, Any] | None | Unset - if isinstance(self.schedule_metadata, Unset): - schedule_metadata = UNSET - elif isinstance(self.schedule_metadata, ScheduleMetadataRequest): - schedule_metadata = self.schedule_metadata.to_dict() - else: - schedule_metadata = self.schedule_metadata - - closed_through: None | str | Unset - if isinstance(self.closed_through, Unset): - closed_through = UNSET - elif isinstance(self.closed_through, datetime.date): - closed_through = self.closed_through.isoformat() - else: - closed_through = self.closed_through - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update( - { - "name": name, - "element_ids": element_ids, - "period_start": period_start, - "period_end": period_end, - "monthly_amount": monthly_amount, - "entry_template": entry_template, - } - ) - if taxonomy_id is not UNSET: - field_dict["taxonomy_id"] = taxonomy_id - if schedule_metadata is not UNSET: - field_dict["schedule_metadata"] = schedule_metadata - if closed_through is not UNSET: - field_dict["closed_through"] = closed_through - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.entry_template_request import EntryTemplateRequest - from ..models.schedule_metadata_request import ScheduleMetadataRequest - - d = dict(src_dict) - name = d.pop("name") - - element_ids = cast(list[str], d.pop("element_ids")) - - period_start = isoparse(d.pop("period_start")).date() - - period_end = isoparse(d.pop("period_end")).date() - - monthly_amount = d.pop("monthly_amount") - - entry_template = EntryTemplateRequest.from_dict(d.pop("entry_template")) - - def _parse_taxonomy_id(data: object) -> None | str | Unset: - if data is None: - return data - if isinstance(data, Unset): - return data - return cast(None | str | Unset, data) - - taxonomy_id = _parse_taxonomy_id(d.pop("taxonomy_id", UNSET)) - - def _parse_schedule_metadata( - data: object, - ) -> None | ScheduleMetadataRequest | Unset: - if data is None: - return data - if isinstance(data, Unset): - return data - try: - if not isinstance(data, dict): - raise TypeError() - schedule_metadata_type_0 = ScheduleMetadataRequest.from_dict(data) - - return schedule_metadata_type_0 - except (TypeError, ValueError, AttributeError, KeyError): - pass - return cast(None | ScheduleMetadataRequest | Unset, data) - - schedule_metadata = _parse_schedule_metadata(d.pop("schedule_metadata", UNSET)) - - def _parse_closed_through(data: object) -> datetime.date | None | Unset: - if data is None: - return data - if isinstance(data, Unset): - return data - try: - if not isinstance(data, str): - raise TypeError() - closed_through_type_0 = isoparse(data).date() - - return closed_through_type_0 - except (TypeError, ValueError, AttributeError, KeyError): - pass - return cast(datetime.date | None | Unset, data) - - closed_through = _parse_closed_through(d.pop("closed_through", UNSET)) - - create_schedule_request = cls( - name=name, - element_ids=element_ids, - period_start=period_start, - period_end=period_end, - monthly_amount=monthly_amount, - entry_template=entry_template, - taxonomy_id=taxonomy_id, - schedule_metadata=schedule_metadata, - closed_through=closed_through, - ) - - create_schedule_request.additional_properties = d - return create_schedule_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/delete_information_block_request.py b/robosystems_client/models/delete_information_block_request.py new file mode 100644 index 0000000..25098a6 --- /dev/null +++ b/robosystems_client/models/delete_information_block_request.py @@ -0,0 +1,94 @@ +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.delete_information_block_request_payload import ( + DeleteInformationBlockRequestPayload, + ) + + +T = TypeVar("T", bound="DeleteInformationBlockRequest") + + +@_attrs_define +class DeleteInformationBlockRequest: + """Generic delete request — mirrors :class:`CreateInformationBlockRequest`. + + Validated against the registry entry's ``delete_request_model``. + Block types that don't support deletion raise ``NotImplementedError``. + + Attributes: + block_type (str): Block type discriminator. Must match a registered entry. + payload (DeleteInformationBlockRequestPayload | Unset): Block-type-specific delete payload. Typically carries + just the structure_id. Shape-validated against the registry entry's `delete_request_model` at dispatch time. + """ + + block_type: str + payload: DeleteInformationBlockRequestPayload | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + block_type = self.block_type + + payload: dict[str, Any] | Unset = UNSET + if not isinstance(self.payload, Unset): + payload = self.payload.to_dict() + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "block_type": block_type, + } + ) + if payload is not UNSET: + field_dict["payload"] = payload + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.delete_information_block_request_payload import ( + DeleteInformationBlockRequestPayload, + ) + + d = dict(src_dict) + block_type = d.pop("block_type") + + _payload = d.pop("payload", UNSET) + payload: DeleteInformationBlockRequestPayload | Unset + if isinstance(_payload, Unset): + payload = UNSET + else: + payload = DeleteInformationBlockRequestPayload.from_dict(_payload) + + delete_information_block_request = cls( + block_type=block_type, + payload=payload, + ) + + delete_information_block_request.additional_properties = d + return delete_information_block_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/delete_information_block_request_payload.py b/robosystems_client/models/delete_information_block_request_payload.py new file mode 100644 index 0000000..6018d08 --- /dev/null +++ b/robosystems_client/models/delete_information_block_request_payload.py @@ -0,0 +1,50 @@ +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="DeleteInformationBlockRequestPayload") + + +@_attrs_define +class DeleteInformationBlockRequestPayload: + """Block-type-specific delete payload. Typically carries just the structure_id. Shape-validated against the registry + entry's `delete_request_model` at dispatch time. + + """ + + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + delete_information_block_request_payload = cls() + + delete_information_block_request_payload.additional_properties = d + return delete_information_block_request_payload + + @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/entry_template_request.py b/robosystems_client/models/entry_template_request.py deleted file mode 100644 index ac35d55..0000000 --- a/robosystems_client/models/entry_template_request.py +++ /dev/null @@ -1,109 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any, TypeVar - -from attrs import define as _attrs_define -from attrs import field as _attrs_field - -from ..models.entry_template_request_entry_type import EntryTemplateRequestEntryType -from ..types import UNSET, Unset - -T = TypeVar("T", bound="EntryTemplateRequest") - - -@_attrs_define -class EntryTemplateRequest: - """ - Attributes: - debit_element_id (str): Element to debit (e.g., Depreciation Expense) - credit_element_id (str): Element to credit (e.g., Accumulated Depreciation) - entry_type (EntryTemplateRequestEntryType | Unset): Entry type for generated entries Default: - EntryTemplateRequestEntryType.CLOSING. - memo_template (str | Unset): Memo template ({structure_name} is replaced) Default: ''. - auto_reverse (bool | Unset): Auto-generate a reversing entry on the first day of the next period Default: False. - """ - - debit_element_id: str - credit_element_id: str - entry_type: EntryTemplateRequestEntryType | Unset = ( - EntryTemplateRequestEntryType.CLOSING - ) - memo_template: str | Unset = "" - auto_reverse: bool | Unset = False - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - debit_element_id = self.debit_element_id - - credit_element_id = self.credit_element_id - - entry_type: str | Unset = UNSET - if not isinstance(self.entry_type, Unset): - entry_type = self.entry_type.value - - memo_template = self.memo_template - - auto_reverse = self.auto_reverse - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update( - { - "debit_element_id": debit_element_id, - "credit_element_id": credit_element_id, - } - ) - if entry_type is not UNSET: - field_dict["entry_type"] = entry_type - if memo_template is not UNSET: - field_dict["memo_template"] = memo_template - if auto_reverse is not UNSET: - field_dict["auto_reverse"] = auto_reverse - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - debit_element_id = d.pop("debit_element_id") - - credit_element_id = d.pop("credit_element_id") - - _entry_type = d.pop("entry_type", UNSET) - entry_type: EntryTemplateRequestEntryType | Unset - if isinstance(_entry_type, Unset): - entry_type = UNSET - else: - entry_type = EntryTemplateRequestEntryType(_entry_type) - - memo_template = d.pop("memo_template", UNSET) - - auto_reverse = d.pop("auto_reverse", UNSET) - - entry_template_request = cls( - debit_element_id=debit_element_id, - credit_element_id=credit_element_id, - entry_type=entry_type, - memo_template=memo_template, - auto_reverse=auto_reverse, - ) - - entry_template_request.additional_properties = d - return entry_template_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/entry_template_request_entry_type.py b/robosystems_client/models/entry_template_request_entry_type.py deleted file mode 100644 index e9b043f..0000000 --- a/robosystems_client/models/entry_template_request_entry_type.py +++ /dev/null @@ -1,11 +0,0 @@ -from enum import Enum - - -class EntryTemplateRequestEntryType(str, Enum): - ADJUSTING = "adjusting" - CLOSING = "closing" - REVERSING = "reversing" - STANDARD = "standard" - - def __str__(self) -> str: - return str(self.value) diff --git a/robosystems_client/models/evaluate_rules_request.py b/robosystems_client/models/evaluate_rules_request.py new file mode 100644 index 0000000..158f716 --- /dev/null +++ b/robosystems_client/models/evaluate_rules_request.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +import datetime +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field +from dateutil.parser import isoparse + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="EvaluateRulesRequest") + + +@_attrs_define +class EvaluateRulesRequest: + """Request body for the ``evaluate-rules`` operation (Phase delta.3). + + Runs every rule scoped to ``structure_id`` (plus element/association- + scoped rules for the structure's atoms), binds ``$Variable`` references + to facts via qname lookup, and writes one + :class:`VerificationResult` row per rule. + + Optional ``period_start`` / ``period_end`` narrow the fact-binding + window; without them the engine uses the most recent ``in_scope`` fact + for each element regardless of period. + + Attributes: + structure_id (str): + fact_set_id (None | str | Unset): Optional FactSet id to stamp on each VerificationResult row. Allows results to + be scoped to a specific period run when the FactSet table is populated (Phase zeta expand pass). + period_start (datetime.date | None | Unset): Lower bound on the fact period window (inclusive). + period_end (datetime.date | None | Unset): Upper bound on the fact period window (inclusive). + """ + + structure_id: str + fact_set_id: None | str | Unset = UNSET + period_start: datetime.date | None | Unset = UNSET + period_end: datetime.date | None | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + structure_id = self.structure_id + + fact_set_id: None | str | Unset + if isinstance(self.fact_set_id, Unset): + fact_set_id = UNSET + else: + fact_set_id = self.fact_set_id + + period_start: None | str | Unset + if isinstance(self.period_start, Unset): + period_start = UNSET + elif isinstance(self.period_start, datetime.date): + period_start = self.period_start.isoformat() + else: + period_start = self.period_start + + period_end: None | str | Unset + if isinstance(self.period_end, Unset): + period_end = UNSET + elif isinstance(self.period_end, datetime.date): + period_end = self.period_end.isoformat() + else: + period_end = self.period_end + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "structure_id": structure_id, + } + ) + if fact_set_id is not UNSET: + field_dict["fact_set_id"] = fact_set_id + if period_start is not UNSET: + field_dict["period_start"] = period_start + if period_end is not UNSET: + field_dict["period_end"] = period_end + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + structure_id = d.pop("structure_id") + + def _parse_fact_set_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + fact_set_id = _parse_fact_set_id(d.pop("fact_set_id", UNSET)) + + def _parse_period_start(data: object) -> datetime.date | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, str): + raise TypeError() + period_start_type_0 = isoparse(data).date() + + return period_start_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(datetime.date | None | Unset, data) + + period_start = _parse_period_start(d.pop("period_start", UNSET)) + + def _parse_period_end(data: object) -> datetime.date | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, str): + raise TypeError() + period_end_type_0 = isoparse(data).date() + + return period_end_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(datetime.date | None | Unset, data) + + period_end = _parse_period_end(d.pop("period_end", UNSET)) + + evaluate_rules_request = cls( + structure_id=structure_id, + fact_set_id=fact_set_id, + period_start=period_start, + period_end=period_end, + ) + + evaluate_rules_request.additional_properties = d + return evaluate_rules_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/schedule_metadata_request.py b/robosystems_client/models/schedule_metadata_request.py deleted file mode 100644 index 35673b7..0000000 --- a/robosystems_client/models/schedule_metadata_request.py +++ /dev/null @@ -1,108 +0,0 @@ -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="ScheduleMetadataRequest") - - -@_attrs_define -class ScheduleMetadataRequest: - """ - Attributes: - method (str | Unset): Calculation method Default: 'straight_line'. - original_amount (int | Unset): Cost basis in cents Default: 0. - residual_value (int | Unset): Salvage value in cents Default: 0. - useful_life_months (int | Unset): Useful life in months Default: 0. - asset_element_id (None | str | Unset): BS asset element for net book value - """ - - method: str | Unset = "straight_line" - original_amount: int | Unset = 0 - residual_value: int | Unset = 0 - useful_life_months: int | Unset = 0 - asset_element_id: None | str | Unset = UNSET - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - method = self.method - - original_amount = self.original_amount - - residual_value = self.residual_value - - useful_life_months = self.useful_life_months - - asset_element_id: None | str | Unset - if isinstance(self.asset_element_id, Unset): - asset_element_id = UNSET - else: - asset_element_id = self.asset_element_id - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update({}) - if method is not UNSET: - field_dict["method"] = method - if original_amount is not UNSET: - field_dict["original_amount"] = original_amount - if residual_value is not UNSET: - field_dict["residual_value"] = residual_value - if useful_life_months is not UNSET: - field_dict["useful_life_months"] = useful_life_months - if asset_element_id is not UNSET: - field_dict["asset_element_id"] = asset_element_id - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - method = d.pop("method", UNSET) - - original_amount = d.pop("original_amount", UNSET) - - residual_value = d.pop("residual_value", UNSET) - - useful_life_months = d.pop("useful_life_months", UNSET) - - def _parse_asset_element_id(data: object) -> None | str | Unset: - if data is None: - return data - if isinstance(data, Unset): - return data - return cast(None | str | Unset, data) - - asset_element_id = _parse_asset_element_id(d.pop("asset_element_id", UNSET)) - - schedule_metadata_request = cls( - method=method, - original_amount=original_amount, - residual_value=residual_value, - useful_life_months=useful_life_months, - asset_element_id=asset_element_id, - ) - - schedule_metadata_request.additional_properties = d - return schedule_metadata_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/update_information_block_request.py b/robosystems_client/models/update_information_block_request.py new file mode 100644 index 0000000..6bc1d2c --- /dev/null +++ b/robosystems_client/models/update_information_block_request.py @@ -0,0 +1,97 @@ +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.update_information_block_request_payload import ( + UpdateInformationBlockRequestPayload, + ) + + +T = TypeVar("T", bound="UpdateInformationBlockRequest") + + +@_attrs_define +class UpdateInformationBlockRequest: + """Generic update request — mirrors :class:`CreateInformationBlockRequest`. + + Validated against the registry entry's ``update_request_model``. + Block types that don't support updates (e.g. the statement family, + whose Structures are library-seeded) surface ``NotImplementedError`` + from their dispatch handler, which the registrar maps to HTTP 501. + + Attributes: + block_type (str): Block type discriminator. Must match a registered entry. + payload (UpdateInformationBlockRequestPayload | Unset): Block-type-specific update payload. Typically carries + the structure_id plus whichever fields are editable for this block type. Shape-validated against the registry + entry's `update_request_model` at dispatch time. + """ + + block_type: str + payload: UpdateInformationBlockRequestPayload | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + block_type = self.block_type + + payload: dict[str, Any] | Unset = UNSET + if not isinstance(self.payload, Unset): + payload = self.payload.to_dict() + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "block_type": block_type, + } + ) + if payload is not UNSET: + field_dict["payload"] = payload + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.update_information_block_request_payload import ( + UpdateInformationBlockRequestPayload, + ) + + d = dict(src_dict) + block_type = d.pop("block_type") + + _payload = d.pop("payload", UNSET) + payload: UpdateInformationBlockRequestPayload | Unset + if isinstance(_payload, Unset): + payload = UNSET + else: + payload = UpdateInformationBlockRequestPayload.from_dict(_payload) + + update_information_block_request = cls( + block_type=block_type, + payload=payload, + ) + + update_information_block_request.additional_properties = d + return update_information_block_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/update_information_block_request_payload.py b/robosystems_client/models/update_information_block_request_payload.py new file mode 100644 index 0000000..34c40b6 --- /dev/null +++ b/robosystems_client/models/update_information_block_request_payload.py @@ -0,0 +1,50 @@ +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="UpdateInformationBlockRequestPayload") + + +@_attrs_define +class UpdateInformationBlockRequestPayload: + """Block-type-specific update payload. Typically carries the structure_id plus whichever fields are editable for this + block type. Shape-validated against the registry entry's `update_request_model` at dispatch time. + + """ + + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + update_information_block_request_payload = cls() + + update_information_block_request_payload.additional_properties = d + return update_information_block_request_payload + + @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/update_schedule_request.py b/robosystems_client/models/update_schedule_request.py deleted file mode 100644 index 470a8d1..0000000 --- a/robosystems_client/models/update_schedule_request.py +++ /dev/null @@ -1,166 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar, cast - -from attrs import define as _attrs_define -from attrs import field as _attrs_field - -from ..types import UNSET, Unset - -if TYPE_CHECKING: - from ..models.entry_template_request import EntryTemplateRequest - from ..models.schedule_metadata_request import ScheduleMetadataRequest - - -T = TypeVar("T", bound="UpdateScheduleRequest") - - -@_attrs_define -class UpdateScheduleRequest: - """Update mutable fields on a schedule. - - Editable: name, entry_template, schedule_metadata (all live on the - Structure row / its metadata_ JSONB column). - - NOT editable via this op: period_start, period_end, monthly_amount. - Those require fact regeneration — use truncate-schedule (end early) - and create-schedule (start new) instead. - - Omitted fields are left unchanged. - - Attributes: - structure_id (str): - name (None | str | Unset): - entry_template (EntryTemplateRequest | None | Unset): - schedule_metadata (None | ScheduleMetadataRequest | Unset): - """ - - structure_id: str - name: None | str | Unset = UNSET - entry_template: EntryTemplateRequest | None | Unset = UNSET - schedule_metadata: None | ScheduleMetadataRequest | Unset = UNSET - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - from ..models.entry_template_request import EntryTemplateRequest - from ..models.schedule_metadata_request import ScheduleMetadataRequest - - structure_id = self.structure_id - - name: None | str | Unset - if isinstance(self.name, Unset): - name = UNSET - else: - name = self.name - - entry_template: dict[str, Any] | None | Unset - if isinstance(self.entry_template, Unset): - entry_template = UNSET - elif isinstance(self.entry_template, EntryTemplateRequest): - entry_template = self.entry_template.to_dict() - else: - entry_template = self.entry_template - - schedule_metadata: dict[str, Any] | None | Unset - if isinstance(self.schedule_metadata, Unset): - schedule_metadata = UNSET - elif isinstance(self.schedule_metadata, ScheduleMetadataRequest): - schedule_metadata = self.schedule_metadata.to_dict() - else: - schedule_metadata = self.schedule_metadata - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update( - { - "structure_id": structure_id, - } - ) - if name is not UNSET: - field_dict["name"] = name - if entry_template is not UNSET: - field_dict["entry_template"] = entry_template - if schedule_metadata is not UNSET: - field_dict["schedule_metadata"] = schedule_metadata - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.entry_template_request import EntryTemplateRequest - from ..models.schedule_metadata_request import ScheduleMetadataRequest - - d = dict(src_dict) - structure_id = d.pop("structure_id") - - def _parse_name(data: object) -> None | str | Unset: - if data is None: - return data - if isinstance(data, Unset): - return data - return cast(None | str | Unset, data) - - name = _parse_name(d.pop("name", UNSET)) - - def _parse_entry_template(data: object) -> EntryTemplateRequest | None | Unset: - if data is None: - return data - if isinstance(data, Unset): - return data - try: - if not isinstance(data, dict): - raise TypeError() - entry_template_type_0 = EntryTemplateRequest.from_dict(data) - - return entry_template_type_0 - except (TypeError, ValueError, AttributeError, KeyError): - pass - return cast(EntryTemplateRequest | None | Unset, data) - - entry_template = _parse_entry_template(d.pop("entry_template", UNSET)) - - def _parse_schedule_metadata( - data: object, - ) -> None | ScheduleMetadataRequest | Unset: - if data is None: - return data - if isinstance(data, Unset): - return data - try: - if not isinstance(data, dict): - raise TypeError() - schedule_metadata_type_0 = ScheduleMetadataRequest.from_dict(data) - - return schedule_metadata_type_0 - except (TypeError, ValueError, AttributeError, KeyError): - pass - return cast(None | ScheduleMetadataRequest | Unset, data) - - schedule_metadata = _parse_schedule_metadata(d.pop("schedule_metadata", UNSET)) - - update_schedule_request = cls( - structure_id=structure_id, - name=name, - entry_template=entry_template, - schedule_metadata=schedule_metadata, - ) - - update_schedule_request.additional_properties = d - return update_schedule_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/tests/test_ledger_client.py b/tests/test_ledger_client.py index 970cb4a..fe81380 100644 --- a/tests/test_ledger_client.py +++ b/tests/test_ledger_client.py @@ -306,10 +306,10 @@ def test_close_period(self, mock_op, mock_config, graph_id): assert body.period == "2026-03" assert body.allow_stale_sync is True - @patch("robosystems_client.clients.ledger_client.op_create_schedule") + @patch("robosystems_client.clients.ledger_client.op_create_information_block") def test_create_schedule(self, mock_op, mock_config, graph_id): envelope = _envelope( - "create-schedule", + "create-information-block", { "structure_id": "str_1", "name": "Depreciation", @@ -331,6 +331,8 @@ def test_create_schedule(self, mock_op, mock_config, graph_id): credit_element_id="elem_accum_depr", ) assert result["total_periods"] == 36 + body = mock_op.call_args.kwargs["body"] + assert body.block_type == "schedule" @patch("robosystems_client.clients.ledger_client.op_auto_map_elements") def test_auto_map_elements_returns_ack(self, mock_op, mock_config, graph_id): @@ -974,61 +976,99 @@ def test_get_mapping_coverage(self, mock_execute, mock_config, graph_id): assert variables["mappingId"] == "map_1" @patch("robosystems_client.graphql.client.GraphQLClient.execute") - def test_list_schedules(self, mock_execute, mock_config, graph_id): + def test_list_information_blocks(self, mock_execute, mock_config, graph_id): mock_execute.return_value = { - "schedules": { - "schedules": [ - { - "structureId": "str_sched_1", - "name": "Depreciation", - "taxonomyName": "My CoA", - "entryTemplate": {}, - "scheduleMetadata": {}, - "totalPeriods": 36, - "periodsWithEntries": 3, - } - ] - } + "informationBlocks": [ + { + "id": "str_sched_1", + "blockType": "schedule", + "name": "Depreciation", + "displayName": "Schedule", + "category": "Close", + "taxonomyId": "tax_01", + "taxonomyName": "My CoA", + "informationModel": { + "conceptArrangement": "roll_forward", + "memberArrangement": None, + }, + "artifact": { + "topic": None, + "parentheticalNote": None, + "template": None, + "mechanics": { + "kind": "closing_entry_generator", + "entryTemplate": {}, + "scheduleMetadata": {}, + "periodsWithEntries": 3, + }, + }, + "elements": [], + "connections": [], + "facts": [], + } + ] } client = LedgerClient(mock_config) - result = client.list_schedules(graph_id) + result = client.list_information_blocks(graph_id, block_type="schedule") assert len(result) == 1 - assert result[0]["structure_id"] == "str_sched_1" - assert result[0]["total_periods"] == 36 + assert result[0]["id"] == "str_sched_1" + assert result[0]["block_type"] == "schedule" + assert result[0]["taxonomy_name"] == "My CoA" + variables = mock_execute.call_args[0][2] + assert variables["blockType"] == "schedule" @patch("robosystems_client.graphql.client.GraphQLClient.execute") - def test_get_schedule_facts(self, mock_execute, mock_config, graph_id): + def test_get_information_block(self, mock_execute, mock_config, graph_id): mock_execute.return_value = { - "scheduleFacts": { - "structureId": "str_sched_1", + "informationBlock": { + "id": "struct_sched_1", + "blockType": "schedule", + "name": "Depreciation", + "displayName": "Schedule", + "category": "Close", + "informationModel": { + "conceptArrangement": "roll_forward", + "memberArrangement": None, + }, + "artifact": { + "topic": None, + "parentheticalNote": None, + "template": None, + "mechanics": {"kind": "closing_entry_generator"}, + }, + "elements": [], + "connections": [], "facts": [ { + "id": "fact_1", "elementId": "elem_depr", - "elementName": "Depreciation Expense", "value": 100000, "periodStart": "2026-01-01", "periodEnd": "2026-01-31", + "periodType": "duration", + "unit": "USD", + "factScope": "in_scope", + "factSetId": None, } ], } } client = LedgerClient(mock_config) - result = client.get_schedule_facts( - graph_id, "str_sched_1", period_start="2026-01-01", period_end="2026-03-31" - ) - assert len(result) == 1 - assert result[0]["value"] == 100000 + block = client.get_information_block(graph_id, "struct_sched_1") + assert block is not None + assert block["id"] == "struct_sched_1" + assert block["block_type"] == "schedule" + assert block["facts"][0]["value"] == 100000 variables = mock_execute.call_args[0][2] - assert variables["structureId"] == "str_sched_1" - assert variables["periodStart"] == "2026-01-01" + assert variables["id"] == "struct_sched_1" @patch("robosystems_client.graphql.client.GraphQLClient.execute") - def test_get_schedule_facts_returns_empty_list_when_none( + def test_get_information_block_returns_none_when_missing( self, mock_execute, mock_config, graph_id ): - mock_execute.return_value = {"scheduleFacts": None} + mock_execute.return_value = {"informationBlock": None} client = LedgerClient(mock_config) - assert client.get_schedule_facts(graph_id, "str_1") == [] + assert client.get_information_block(graph_id, "struct_missing") is None @patch("robosystems_client.graphql.client.GraphQLClient.execute") def test_get_period_close_status(self, mock_execute, mock_config, graph_id): @@ -1368,35 +1408,37 @@ def test_delete_association_returns_result_when_present( @pytest.mark.unit class TestScheduleAdditionalOps: - @patch("robosystems_client.clients.ledger_client.op_update_schedule") + @patch("robosystems_client.clients.ledger_client.op_update_information_block") def test_update_schedule(self, mock_op, mock_config, graph_id): envelope = _envelope( - "update-schedule", + "update-information-block", {"structure_id": "str_sched_1", "name": "Renamed Schedule"}, ) mock_op.return_value = _mock_response(envelope) client = LedgerClient(mock_config) result = client.update_schedule( - graph_id, {"structure_id": "str_sched_1", "name": "Renamed Schedule"} + graph_id, "str_sched_1", {"name": "Renamed Schedule"} ) assert result["name"] == "Renamed Schedule" assert mock_op.call_args.kwargs["graph_id"] == graph_id + body = mock_op.call_args.kwargs["body"] + assert body.block_type == "schedule" - @patch("robosystems_client.clients.ledger_client.op_delete_schedule") + @patch("robosystems_client.clients.ledger_client.op_delete_information_block") def test_delete_schedule_returns_sentinel(self, mock_op, mock_config, graph_id): - envelope = _envelope("delete-schedule", None) + envelope = _envelope("delete-information-block", None) mock_op.return_value = _mock_response(envelope) client = LedgerClient(mock_config) result = client.delete_schedule(graph_id, "str_sched_1") assert result == {"deleted": True} body = mock_op.call_args.kwargs["body"] - assert body.structure_id == "str_sched_1" + assert body.block_type == "schedule" - @patch("robosystems_client.clients.ledger_client.op_delete_schedule") + @patch("robosystems_client.clients.ledger_client.op_delete_information_block") def test_delete_schedule_returns_result_when_present( self, mock_op, mock_config, graph_id ): - envelope = _envelope("delete-schedule", {"structure_id": "str_sched_1"}) + envelope = _envelope("delete-information-block", {"structure_id": "str_sched_1"}) mock_op.return_value = _mock_response(envelope) client = LedgerClient(mock_config) result = client.delete_schedule(graph_id, "str_sched_1")