From 37f1bf90f74a25893c981c234628f7d9228cc67a Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Sat, 20 Jun 2026 03:04:35 -0500 Subject: [PATCH 1/2] chore(sdk): regenerate from latest OpenAPI spec Adds the new rebuild-schedule operation surface: op_rebuild_schedule, RebuildScheduleRequest, ScheduleCreatedResponse, and the matching OperationEnvelope wrapper. Also refreshes the update-schedule docstrings to point at create-information-block (block_type='schedule'). --- .../op_rebuild_schedule.py | 330 ++++++++++++++++++ robosystems_client/models/__init__.py | 16 + ...tion_envelope_schedule_created_response.py | 158 +++++++++ ...velope_schedule_created_response_status.py | 10 + .../models/rebuild_schedule_request.py | 74 ++++ .../models/schedule_created_response.py | 172 +++++++++ ...le_created_response_rule_summary_type_0.py | 47 +++ .../models/update_schedule_arm.py | 2 +- .../models/update_schedule_request.py | 2 +- 9 files changed, 809 insertions(+), 2 deletions(-) create mode 100644 robosystems_client/api/extensions_robo_ledger/op_rebuild_schedule.py create mode 100644 robosystems_client/models/operation_envelope_schedule_created_response.py create mode 100644 robosystems_client/models/operation_envelope_schedule_created_response_status.py create mode 100644 robosystems_client/models/rebuild_schedule_request.py create mode 100644 robosystems_client/models/schedule_created_response.py create mode 100644 robosystems_client/models/schedule_created_response_rule_summary_type_0.py diff --git a/robosystems_client/api/extensions_robo_ledger/op_rebuild_schedule.py b/robosystems_client/api/extensions_robo_ledger/op_rebuild_schedule.py new file mode 100644 index 0000000..2b387a7 --- /dev/null +++ b/robosystems_client/api/extensions_robo_ledger/op_rebuild_schedule.py @@ -0,0 +1,330 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.error_response import ErrorResponse +from ...models.operation_envelope_schedule_created_response import ( + OperationEnvelopeScheduleCreatedResponse, +) +from ...models.rebuild_schedule_request import RebuildScheduleRequest +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + graph_id: str, + *, + body: RebuildScheduleRequest, + 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/rebuild-schedule".format( + graph_id=quote(str(graph_id), safe=""), + ), + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> ErrorResponse | OperationEnvelopeScheduleCreatedResponse | None: + if response.status_code == 200: + response_200 = OperationEnvelopeScheduleCreatedResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 400: + response_400 = ErrorResponse.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = ErrorResponse.from_dict(response.json()) + + return response_401 + + if response.status_code == 403: + response_403 = ErrorResponse.from_dict(response.json()) + + return response_403 + + if response.status_code == 404: + response_404 = ErrorResponse.from_dict(response.json()) + + return response_404 + + if response.status_code == 409: + response_409 = ErrorResponse.from_dict(response.json()) + + return response_409 + + if response.status_code == 422: + response_422 = ErrorResponse.from_dict(response.json()) + + return response_422 + + if response.status_code == 429: + response_429 = ErrorResponse.from_dict(response.json()) + + return response_429 + + if response.status_code == 500: + response_500 = ErrorResponse.from_dict(response.json()) + + return response_500 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[ErrorResponse | OperationEnvelopeScheduleCreatedResponse]: + 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: RebuildScheduleRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelopeScheduleCreatedResponse]: + """Rebuild Schedule In Place + + Re-run the schedule generator in place on an existing schedule. Atomic alternative to delete-then- + recreate (which orphans pending obligations): preserves the structure id + element associations + + taxonomy, voids the old pending obligation chain, deletes the old facts and SumEquals rules, and + regenerates fresh forward facts + a fresh obligation chain from the schedule's stored definition + (entry_template / schedule_metadata / monthly_amount / period bounds). The historical-vs-in-scope + split is re-derived from the CURRENT fiscal calendar closed_through. Use this to pick up a fixed + generator (e.g. the roll-forward direction fix) without orphaning obligations. + + **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 (RebuildScheduleRequest): Re-run the schedule generator in place on an existing + schedule. + + Atomic alternative to delete-then-recreate: the structure id and its + element associations are preserved, the old pending obligation chain + is voided, the old facts + rules are deleted, and a fresh set of + forward facts + a fresh obligation chain are regenerated from the + schedule's stored definition (entry_template / schedule_metadata / + monthly_amount / period bounds on the Structure's metadata). + + The historical-vs-in-scope split is re-derived from the CURRENT fiscal + calendar `closed_through`, so a rebuild re-scopes the schedule to + today's close state. Use this to pick up a fixed generator (e.g. the + roll-forward direction fix) without orphaning obligations. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ErrorResponse | OperationEnvelopeScheduleCreatedResponse] + """ + + 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: RebuildScheduleRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelopeScheduleCreatedResponse | None: + """Rebuild Schedule In Place + + Re-run the schedule generator in place on an existing schedule. Atomic alternative to delete-then- + recreate (which orphans pending obligations): preserves the structure id + element associations + + taxonomy, voids the old pending obligation chain, deletes the old facts and SumEquals rules, and + regenerates fresh forward facts + a fresh obligation chain from the schedule's stored definition + (entry_template / schedule_metadata / monthly_amount / period bounds). The historical-vs-in-scope + split is re-derived from the CURRENT fiscal calendar closed_through. Use this to pick up a fixed + generator (e.g. the roll-forward direction fix) without orphaning obligations. + + **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 (RebuildScheduleRequest): Re-run the schedule generator in place on an existing + schedule. + + Atomic alternative to delete-then-recreate: the structure id and its + element associations are preserved, the old pending obligation chain + is voided, the old facts + rules are deleted, and a fresh set of + forward facts + a fresh obligation chain are regenerated from the + schedule's stored definition (entry_template / schedule_metadata / + monthly_amount / period bounds on the Structure's metadata). + + The historical-vs-in-scope split is re-derived from the CURRENT fiscal + calendar `closed_through`, so a rebuild re-scopes the schedule to + today's close state. Use this to pick up a fixed generator (e.g. the + roll-forward direction fix) without orphaning obligations. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ErrorResponse | OperationEnvelopeScheduleCreatedResponse + """ + + 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: RebuildScheduleRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | OperationEnvelopeScheduleCreatedResponse]: + """Rebuild Schedule In Place + + Re-run the schedule generator in place on an existing schedule. Atomic alternative to delete-then- + recreate (which orphans pending obligations): preserves the structure id + element associations + + taxonomy, voids the old pending obligation chain, deletes the old facts and SumEquals rules, and + regenerates fresh forward facts + a fresh obligation chain from the schedule's stored definition + (entry_template / schedule_metadata / monthly_amount / period bounds). The historical-vs-in-scope + split is re-derived from the CURRENT fiscal calendar closed_through. Use this to pick up a fixed + generator (e.g. the roll-forward direction fix) without orphaning obligations. + + **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 (RebuildScheduleRequest): Re-run the schedule generator in place on an existing + schedule. + + Atomic alternative to delete-then-recreate: the structure id and its + element associations are preserved, the old pending obligation chain + is voided, the old facts + rules are deleted, and a fresh set of + forward facts + a fresh obligation chain are regenerated from the + schedule's stored definition (entry_template / schedule_metadata / + monthly_amount / period bounds on the Structure's metadata). + + The historical-vs-in-scope split is re-derived from the CURRENT fiscal + calendar `closed_through`, so a rebuild re-scopes the schedule to + today's close state. Use this to pick up a fixed generator (e.g. the + roll-forward direction fix) without orphaning obligations. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ErrorResponse | OperationEnvelopeScheduleCreatedResponse] + """ + + 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: RebuildScheduleRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | OperationEnvelopeScheduleCreatedResponse | None: + """Rebuild Schedule In Place + + Re-run the schedule generator in place on an existing schedule. Atomic alternative to delete-then- + recreate (which orphans pending obligations): preserves the structure id + element associations + + taxonomy, voids the old pending obligation chain, deletes the old facts and SumEquals rules, and + regenerates fresh forward facts + a fresh obligation chain from the schedule's stored definition + (entry_template / schedule_metadata / monthly_amount / period bounds). The historical-vs-in-scope + split is re-derived from the CURRENT fiscal calendar closed_through. Use this to pick up a fixed + generator (e.g. the roll-forward direction fix) without orphaning obligations. + + **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 (RebuildScheduleRequest): Re-run the schedule generator in place on an existing + schedule. + + Atomic alternative to delete-then-recreate: the structure id and its + element associations are preserved, the old pending obligation chain + is voided, the old facts + rules are deleted, and a fresh set of + forward facts + a fresh obligation chain are regenerated from the + schedule's stored definition (entry_template / schedule_metadata / + monthly_amount / period bounds on the Structure's metadata). + + The historical-vs-in-scope split is re-derived from the CURRENT fiscal + calendar `closed_through`, so a rebuild re-scopes the schedule to + today's close state. Use this to pick up a fixed generator (e.g. the + roll-forward direction fix) without orphaning obligations. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ErrorResponse | OperationEnvelopeScheduleCreatedResponse + """ + + return ( + await asyncio_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ) + ).parsed diff --git a/robosystems_client/models/__init__.py b/robosystems_client/models/__init__.py index 528ea37..062171d 100644 --- a/robosystems_client/models/__init__.py +++ b/robosystems_client/models/__init__.py @@ -409,6 +409,12 @@ from .operation_envelope_report_response_status import ( OperationEnvelopeReportResponseStatus, ) +from .operation_envelope_schedule_created_response import ( + OperationEnvelopeScheduleCreatedResponse, +) +from .operation_envelope_schedule_created_response_status import ( + OperationEnvelopeScheduleCreatedResponseStatus, +) from .operation_envelope_security_response import OperationEnvelopeSecurityResponse from .operation_envelope_security_response_status import ( OperationEnvelopeSecurityResponseStatus, @@ -504,6 +510,7 @@ from .query_limits import QueryLimits from .quick_books_connection_config import QuickBooksConnectionConfig from .rate_limits import RateLimits +from .rebuild_schedule_request import RebuildScheduleRequest from .regenerate_report_operation import RegenerateReportOperation from .register_request import RegisterRequest from .remove_publish_list_member_operation import RemovePublishListMemberOperation @@ -527,6 +534,10 @@ from .rule_lite import RuleLite from .rule_target_lite import RuleTargetLite from .rule_variable_lite import RuleVariableLite +from .schedule_created_response import ScheduleCreatedResponse +from .schedule_created_response_rule_summary_type_0 import ( + ScheduleCreatedResponseRuleSummaryType0, +) from .schedule_mechanics import ScheduleMechanics from .schedule_metadata_request import ScheduleMetadataRequest from .schema_export_response import SchemaExportResponse @@ -978,6 +989,8 @@ "OperationEnvelopePublishListResponseStatus", "OperationEnvelopeReportResponse", "OperationEnvelopeReportResponseStatus", + "OperationEnvelopeScheduleCreatedResponse", + "OperationEnvelopeScheduleCreatedResponseStatus", "OperationEnvelopeSecurityResponse", "OperationEnvelopeSecurityResponseStatus", "OperationEnvelopeShareReportResponse", @@ -1049,6 +1062,7 @@ "QueryLimits", "QuickBooksConnectionConfig", "RateLimits", + "RebuildScheduleRequest", "RegenerateReportOperation", "RegisterRequest", "RemovePublishListMemberOperation", @@ -1070,6 +1084,8 @@ "RuleLite", "RuleTargetLite", "RuleVariableLite", + "ScheduleCreatedResponse", + "ScheduleCreatedResponseRuleSummaryType0", "ScheduleMechanics", "ScheduleMetadataRequest", "SchemaExportResponse", diff --git a/robosystems_client/models/operation_envelope_schedule_created_response.py b/robosystems_client/models/operation_envelope_schedule_created_response.py new file mode 100644 index 0000000..f39da34 --- /dev/null +++ b/robosystems_client/models/operation_envelope_schedule_created_response.py @@ -0,0 +1,158 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..models.operation_envelope_schedule_created_response_status import ( + OperationEnvelopeScheduleCreatedResponseStatus, +) +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.schedule_created_response import ScheduleCreatedResponse + + +T = TypeVar("T", bound="OperationEnvelopeScheduleCreatedResponse") + + +@_attrs_define +class OperationEnvelopeScheduleCreatedResponse: + """ + Attributes: + operation (str): Kebab-case operation name + operation_id (str): op_-prefixed ULID for audit and SSE correlation + status (OperationEnvelopeScheduleCreatedResponseStatus): Operation lifecycle state + at (str): ISO-8601 UTC timestamp + result (None | ScheduleCreatedResponse | Unset): Command-specific result payload + created_by (None | str | Unset): User ID that initiated the operation (null for legacy callers) + idempotent_replay (bool | Unset): True when this envelope came from the idempotency cache — the underlying + command did not execute again. False on fresh executions. Default: False. + """ + + operation: str + operation_id: str + status: OperationEnvelopeScheduleCreatedResponseStatus + at: str + result: None | ScheduleCreatedResponse | Unset = UNSET + created_by: None | str | Unset = UNSET + idempotent_replay: bool | Unset = False + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + from ..models.schedule_created_response import ScheduleCreatedResponse + + operation = self.operation + + operation_id = self.operation_id + + status = self.status.value + + at = self.at + + result: dict[str, Any] | None | Unset + if isinstance(self.result, Unset): + result = UNSET + elif isinstance(self.result, ScheduleCreatedResponse): + result = self.result.to_dict() + else: + result = self.result + + created_by: None | str | Unset + if isinstance(self.created_by, Unset): + created_by = UNSET + else: + created_by = self.created_by + + idempotent_replay = self.idempotent_replay + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "operation": operation, + "operationId": operation_id, + "status": status, + "at": at, + } + ) + if result is not UNSET: + field_dict["result"] = result + if created_by is not UNSET: + field_dict["createdBy"] = created_by + if idempotent_replay is not UNSET: + field_dict["idempotentReplay"] = idempotent_replay + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.schedule_created_response import ScheduleCreatedResponse + + d = dict(src_dict) + operation = d.pop("operation") + + operation_id = d.pop("operationId") + + status = OperationEnvelopeScheduleCreatedResponseStatus(d.pop("status")) + + at = d.pop("at") + + def _parse_result(data: object) -> None | ScheduleCreatedResponse | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + result_type_0 = ScheduleCreatedResponse.from_dict(data) + + return result_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(None | ScheduleCreatedResponse | Unset, data) + + result = _parse_result(d.pop("result", UNSET)) + + def _parse_created_by(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + created_by = _parse_created_by(d.pop("createdBy", UNSET)) + + idempotent_replay = d.pop("idempotentReplay", UNSET) + + operation_envelope_schedule_created_response = cls( + operation=operation, + operation_id=operation_id, + status=status, + at=at, + result=result, + created_by=created_by, + idempotent_replay=idempotent_replay, + ) + + operation_envelope_schedule_created_response.additional_properties = d + return operation_envelope_schedule_created_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/robosystems_client/models/operation_envelope_schedule_created_response_status.py b/robosystems_client/models/operation_envelope_schedule_created_response_status.py new file mode 100644 index 0000000..c21a386 --- /dev/null +++ b/robosystems_client/models/operation_envelope_schedule_created_response_status.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class OperationEnvelopeScheduleCreatedResponseStatus(str, Enum): + COMPLETED = "completed" + FAILED = "failed" + PENDING = "pending" + + def __str__(self) -> str: + return str(self.value) diff --git a/robosystems_client/models/rebuild_schedule_request.py b/robosystems_client/models/rebuild_schedule_request.py new file mode 100644 index 0000000..bea5766 --- /dev/null +++ b/robosystems_client/models/rebuild_schedule_request.py @@ -0,0 +1,74 @@ +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="RebuildScheduleRequest") + + +@_attrs_define +class RebuildScheduleRequest: + """Re-run the schedule generator in place on an existing schedule. + + Atomic alternative to delete-then-recreate: the structure id and its + element associations are preserved, the old pending obligation chain + is voided, the old facts + rules are deleted, and a fresh set of + forward facts + a fresh obligation chain are regenerated from the + schedule's stored definition (entry_template / schedule_metadata / + monthly_amount / period bounds on the Structure's metadata). + + The historical-vs-in-scope split is re-derived from the CURRENT fiscal + calendar `closed_through`, so a rebuild re-scopes the schedule to + today's close state. Use this to pick up a fixed generator (e.g. the + roll-forward direction fix) without orphaning obligations. + + Attributes: + structure_id (str): The schedule structure to regenerate in place. + """ + + 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") + + rebuild_schedule_request = cls( + structure_id=structure_id, + ) + + rebuild_schedule_request.additional_properties = d + return rebuild_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/schedule_created_response.py b/robosystems_client/models/schedule_created_response.py new file mode 100644 index 0000000..521764c --- /dev/null +++ b/robosystems_client/models/schedule_created_response.py @@ -0,0 +1,172 @@ +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.schedule_created_response_rule_summary_type_0 import ( + ScheduleCreatedResponseRuleSummaryType0, + ) + + +T = TypeVar("T", bound="ScheduleCreatedResponse") + + +@_attrs_define +class ScheduleCreatedResponse: + """ + Attributes: + structure_id (str): + name (str): + taxonomy_id (str): + total_periods (int): + total_facts (int): + rule_summary (None | ScheduleCreatedResponseRuleSummaryType0 | Unset): + schedule_created_event_id (None | str | Unset): + pending_event_count (int | Unset): Default: 0. + """ + + structure_id: str + name: str + taxonomy_id: str + total_periods: int + total_facts: int + rule_summary: None | ScheduleCreatedResponseRuleSummaryType0 | Unset = UNSET + schedule_created_event_id: None | str | Unset = UNSET + pending_event_count: int | Unset = 0 + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + from ..models.schedule_created_response_rule_summary_type_0 import ( + ScheduleCreatedResponseRuleSummaryType0, + ) + + structure_id = self.structure_id + + name = self.name + + taxonomy_id = self.taxonomy_id + + total_periods = self.total_periods + + total_facts = self.total_facts + + rule_summary: dict[str, Any] | None | Unset + if isinstance(self.rule_summary, Unset): + rule_summary = UNSET + elif isinstance(self.rule_summary, ScheduleCreatedResponseRuleSummaryType0): + rule_summary = self.rule_summary.to_dict() + else: + rule_summary = self.rule_summary + + schedule_created_event_id: None | str | Unset + if isinstance(self.schedule_created_event_id, Unset): + schedule_created_event_id = UNSET + else: + schedule_created_event_id = self.schedule_created_event_id + + pending_event_count = self.pending_event_count + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "structure_id": structure_id, + "name": name, + "taxonomy_id": taxonomy_id, + "total_periods": total_periods, + "total_facts": total_facts, + } + ) + if rule_summary is not UNSET: + field_dict["rule_summary"] = rule_summary + if schedule_created_event_id is not UNSET: + field_dict["schedule_created_event_id"] = schedule_created_event_id + if pending_event_count is not UNSET: + field_dict["pending_event_count"] = pending_event_count + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.schedule_created_response_rule_summary_type_0 import ( + ScheduleCreatedResponseRuleSummaryType0, + ) + + d = dict(src_dict) + structure_id = d.pop("structure_id") + + name = d.pop("name") + + taxonomy_id = d.pop("taxonomy_id") + + total_periods = d.pop("total_periods") + + total_facts = d.pop("total_facts") + + def _parse_rule_summary( + data: object, + ) -> None | ScheduleCreatedResponseRuleSummaryType0 | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + rule_summary_type_0 = ScheduleCreatedResponseRuleSummaryType0.from_dict(data) + + return rule_summary_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(None | ScheduleCreatedResponseRuleSummaryType0 | Unset, data) + + rule_summary = _parse_rule_summary(d.pop("rule_summary", UNSET)) + + def _parse_schedule_created_event_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + schedule_created_event_id = _parse_schedule_created_event_id( + d.pop("schedule_created_event_id", UNSET) + ) + + pending_event_count = d.pop("pending_event_count", UNSET) + + schedule_created_response = cls( + structure_id=structure_id, + name=name, + taxonomy_id=taxonomy_id, + total_periods=total_periods, + total_facts=total_facts, + rule_summary=rule_summary, + schedule_created_event_id=schedule_created_event_id, + pending_event_count=pending_event_count, + ) + + schedule_created_response.additional_properties = d + return schedule_created_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/robosystems_client/models/schedule_created_response_rule_summary_type_0.py b/robosystems_client/models/schedule_created_response_rule_summary_type_0.py new file mode 100644 index 0000000..69e135a --- /dev/null +++ b/robosystems_client/models/schedule_created_response_rule_summary_type_0.py @@ -0,0 +1,47 @@ +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="ScheduleCreatedResponseRuleSummaryType0") + + +@_attrs_define +class ScheduleCreatedResponseRuleSummaryType0: + """ """ + + additional_properties: dict[str, int] = _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) + schedule_created_response_rule_summary_type_0 = cls() + + schedule_created_response_rule_summary_type_0.additional_properties = d + return schedule_created_response_rule_summary_type_0 + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> int: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: int) -> 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_arm.py b/robosystems_client/models/update_schedule_arm.py index 71a1258..b5ff6e3 100644 --- a/robosystems_client/models/update_schedule_arm.py +++ b/robosystems_client/models/update_schedule_arm.py @@ -30,7 +30,7 @@ class UpdateScheduleArm: NOT editable via this op: period_start, period_end, monthly_amount. Those require fact regeneration — fire an event block that terminates the schedule (e.g., `asset_disposed`) and create a fresh schedule via - `create-schedule`. + `create-information-block` (`block_type='schedule'`). Omitted fields are left unchanged. """ diff --git a/robosystems_client/models/update_schedule_request.py b/robosystems_client/models/update_schedule_request.py index f08432b..3f6f984 100644 --- a/robosystems_client/models/update_schedule_request.py +++ b/robosystems_client/models/update_schedule_request.py @@ -26,7 +26,7 @@ class UpdateScheduleRequest: NOT editable via this op: period_start, period_end, monthly_amount. Those require fact regeneration — fire an event block that terminates the schedule (e.g., `asset_disposed`) and create a fresh schedule via - `create-schedule`. + `create-information-block` (`block_type='schedule'`). Omitted fields are left unchanged. From 3b8fff80bdbe9f6cbd5d32075033868ea9e63347 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Sat, 20 Jun 2026 03:04:51 -0500 Subject: [PATCH 2/2] feat(ledger): expose rebuild-schedule on LedgerClient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the new rebuild-schedule operation into the hand-rolled facade as LedgerClient.rebuild_schedule(graph_id, structure_id, *, idempotency_key). Re-runs the schedule generator in place on an existing schedule — preserves the structure id, element associations, and taxonomy while regenerating facts and the obligation chain. Returns ScheduleCreatedResponse. --- robosystems_client/clients/ledger_client.py | 39 ++++++++++++++++++++ tests/test_ledger_client.py | 40 +++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/robosystems_client/clients/ledger_client.py b/robosystems_client/clients/ledger_client.py index 99bd31c..e065476 100644 --- a/robosystems_client/clients/ledger_client.py +++ b/robosystems_client/clients/ledger_client.py @@ -103,6 +103,9 @@ from ..api.extensions_robo_ledger.op_update_information_block import ( sync_detailed as op_update_information_block, ) +from ..api.extensions_robo_ledger.op_rebuild_schedule import ( + sync_detailed as op_rebuild_schedule, +) from ..api.extensions_robo_ledger.op_add_publish_list_members import ( sync_detailed as op_add_publish_list_members, ) @@ -252,6 +255,7 @@ from ..models.delete_rollforward_arm import DeleteRollforwardArm from ..models.delete_schedule_arm import DeleteScheduleArm from ..models.delete_schedule_request import DeleteScheduleRequest +from ..models.rebuild_schedule_request import RebuildScheduleRequest from ..models.update_legacy_arm import UpdateLegacyArm from ..models.update_rollforward_arm import UpdateRollforwardArm from ..models.link_entity_taxonomy_request import LinkEntityTaxonomyRequest @@ -309,6 +313,7 @@ from ..models.publish_list_member_response import PublishListMemberResponse from ..models.publish_list_response import PublishListResponse from ..models.report_response import ReportResponse +from ..models.schedule_created_response import ScheduleCreatedResponse from ..models.share_report_response import ShareReportResponse from ..models.taxonomy_block_envelope import TaxonomyBlockEnvelope @@ -1187,6 +1192,40 @@ def delete_schedule( "Delete schedule", envelope, DeleteInformationBlockResponse ) + def rebuild_schedule( + self, + graph_id: str, + structure_id: str, + *, + idempotency_key: str | None = None, + ) -> ScheduleCreatedResponse: + """Rebuild a schedule in place — re-run the generator on an existing schedule. + + Atomic alternative to delete-then-recreate (which orphans pending + obligations): preserves the structure id, element associations, and + taxonomy; voids the old pending obligation chain; deletes the old + facts + SumEquals rules; and regenerates fresh forward facts + a + fresh obligation chain from the schedule's stored definition + (``entry_template`` / ``schedule_metadata`` / ``monthly_amount`` / + period bounds). The historical-vs-in-scope split is re-derived from + the current fiscal calendar ``closed_through``, so a rebuild re-scopes + the schedule to today's close state. Use this to pick up a fixed + generator without orphaning obligations. + + Supply ``idempotency_key`` to make the call safe to retry — replays + within 24 hours return the same envelope. Reusing the key with a + different body returns HTTP 409. + """ + body = RebuildScheduleRequest(structure_id=structure_id) + response = op_rebuild_schedule( + graph_id=graph_id, + body=body, + client=self._get_client(), + idempotency_key=idempotency_key if idempotency_key is not None else UNSET, + ) + envelope = self._call_op("Rebuild schedule", response) + return self._typed_result("Rebuild schedule", envelope, ScheduleCreatedResponse) + # ── Period close ──────────────────────────────────────────────────── def get_period_close_status( diff --git a/tests/test_ledger_client.py b/tests/test_ledger_client.py index ed4f75a..3c941c7 100644 --- a/tests/test_ledger_client.py +++ b/tests/test_ledger_client.py @@ -1382,6 +1382,46 @@ def test_delete_schedule_returns_result_when_present( result = client.delete_schedule(graph_id, "str_sched_1") assert result["structure_id"] == "str_sched_1" + @patch("robosystems_client.clients.ledger_client.op_rebuild_schedule") + def test_rebuild_schedule(self, mock_op, mock_config, graph_id): + envelope = _envelope( + "rebuild-schedule", + { + "structure_id": "str_sched_1", + "name": "Depreciation", + "taxonomy_id": "tax_dep", + "total_periods": 36, + "total_facts": 36, + }, + ) + mock_op.return_value = _mock_response(envelope) + client = LedgerClient(mock_config) + result = client.rebuild_schedule(graph_id, "str_sched_1") + assert result["total_facts"] == 36 + assert mock_op.call_args.kwargs["graph_id"] == graph_id + body = mock_op.call_args.kwargs["body"] + assert body.structure_id == "str_sched_1" + assert mock_op.call_args.kwargs["idempotency_key"] is UNSET + + @patch("robosystems_client.clients.ledger_client.op_rebuild_schedule") + def test_rebuild_schedule_passes_idempotency_key( + self, mock_op, mock_config, graph_id + ): + envelope = _envelope( + "rebuild-schedule", + { + "structure_id": "str_sched_1", + "name": "Depreciation", + "taxonomy_id": "tax_dep", + "total_periods": 36, + "total_facts": 36, + }, + ) + mock_op.return_value = _mock_response(envelope) + client = LedgerClient(mock_config) + client.rebuild_schedule(graph_id, "str_sched_1", idempotency_key="key-123") + assert mock_op.call_args.kwargs["idempotency_key"] == "key-123" + # ── Period close additional ops ─────────────────────────────────────────