Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions robosystems_client/clients/ledger_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
from ..api.robo_ledger_fiscal_close.set_close_target import (
sync_detailed as op_set_close_target,
)
from ..api.robo_ledger_setup.change_calendar_start import (
sync_detailed as op_change_calendar_start,
)
from ..api.robo_ledger_taxonomy_mapping.create_taxonomy_block import (
sync_detailed as op_create_taxonomy_block,
)
Expand Down Expand Up @@ -579,6 +582,8 @@
from ..models.update_publish_list_operation import UpdatePublishListOperation
from ..models.reopen_period_operation import ReopenPeriodOperation
from ..models.set_close_target_operation import SetCloseTargetOperation
from ..models.change_calendar_start_request import ChangeCalendarStartRequest
from ..models.change_calendar_start_response import ChangeCalendarStartResponse
from ..models.bind_text_block_request import BindTextBlockRequest
from ..models.bind_text_block_response import BindTextBlockResponse
from ..models.create_taxonomy_block_request import CreateTaxonomyBlockRequest
Expand Down Expand Up @@ -2345,6 +2350,30 @@ def set_close_target(
envelope = self._call_op("Set close target", response)
return self._typed_result("Set close target", envelope, FiscalCalendarResponse)

def change_calendar_start(
self,
graph_id: str,
first_open_period: str,
note: str | None = None,
*,
entity_id: str | None = None,
) -> ChangeCalendarStartResponse:
"""Move the calendar's first open month (YYYY-MM), allowed only until the
entity's first close. Earlier opens months back to it; later removes the
leading months, refused while they hold any entry."""
body = ChangeCalendarStartRequest(
first_open_period=first_open_period,
note=note if note is not None else UNSET,
entity_id=entity_id if entity_id is not None else UNSET,
)
response = op_change_calendar_start(
graph_id=graph_id, body=body, client=self._get_client()
)
envelope = self._call_op("Change calendar start", response)
return self._typed_result(
"Change calendar start", envelope, ChangeCalendarStartResponse
)

def close_period(
self,
graph_id: str,
Expand Down Expand Up @@ -2530,19 +2559,22 @@ def preview_reconciliations(
*,
method: PreviewReconciliationsRequestMethod | str | None = None,
include_tied: bool | None = None,
entity_id: str | None = None,
) -> ReconciliationPreviewResponse:
"""Compare the ledger with something outside it. Records nothing.

``method`` picks the check: ``source_ledger`` (the default, which needs a
connected QuickBooks ledger), ``schedule_register`` or ``statement``.
``include_tied`` also returns the accounts that tie.
``include_tied`` also returns the accounts that tie. ``entity_id`` checks
a subsidiary's books; omitted, the group parent's.
"""
body = PreviewReconciliationsRequest(
period=period,
method=(
PreviewReconciliationsRequestMethod(method) if method is not None else UNSET
),
include_tied=include_tied if include_tied is not None else UNSET,
entity_id=entity_id if entity_id is not None else UNSET,
)
response = op_preview_reconciliations(
graph_id=graph_id, body=body, client=self._get_client()
Expand All @@ -2553,11 +2585,14 @@ def preview_reconciliations(
)

def refresh_reconciliations(
self, graph_id: str, period: str
self, graph_id: str, period: str, *, entity_id: str | None = None
) -> ReconciliationListResponse:
"""Run every reconciliation that applies at a period end and record each
result on its block. ``notes`` names any check that could not run."""
body = RefreshReconciliationsRequest(period=period)
result on its block. ``notes`` names any check that could not run.
``entity_id`` runs a subsidiary's; omitted, the group parent's."""
body = RefreshReconciliationsRequest(
period=period, entity_id=entity_id if entity_id is not None else UNSET
)
response = op_refresh_reconciliations(
graph_id=graph_id, body=body, client=self._get_client()
)
Expand All @@ -2575,6 +2610,7 @@ def record_statement_balance(
balance: float,
document_id: str | None = None,
note: str | None = None,
entity_id: str | None = None,
) -> ReconciliationSummary:
"""Record a statement's ending balance for a balance-sheet account and
reconcile the account to it for the period the statement ends in.
Expand All @@ -2583,10 +2619,12 @@ def record_statement_balance(
account's normal direction (money in a bank account, or the amount owed
on a loan or a card). ``as_of`` is the statement's ending date.
``document_id`` names the statement itself, as a document already on
the graph. Writes no books.
the graph. ``entity_id`` names the subsidiary whose account it is;
omitted, the group parent. Writes no books.
"""
body = RecordStatementBalanceRequest(
element_id=element_id,
entity_id=entity_id if entity_id is not None else UNSET,
as_of=datetime.date.fromisoformat(as_of) if isinstance(as_of, str) else as_of,
balance=balance,
document_id=document_id if document_id is not None else UNSET,
Expand Down
49 changes: 49 additions & 0 deletions tests/test_ledger_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3207,6 +3207,55 @@ def test_set_close_target_sends_entity_id(self, mock_op, mock_config, graph_id):
"entity_id": "ent_2",
}

@patch("robosystems_client.clients.ledger_client.op_change_calendar_start")
def test_change_calendar_start(self, mock_op, mock_config, graph_id):
mock_op.return_value = _mock_response(
_envelope(
"change-calendar-start",
{"fiscal_calendar": {}, "periods_created": 7, "periods_removed": 0},
)
)
client = LedgerClient(mock_config)
result = client.change_calendar_start(
graph_id, "2026-02", "history from February", entity_id="ent_2"
)
assert result["periods_created"] == 7
assert mock_op.call_args.kwargs["body"].to_dict() == {
"first_open_period": "2026-02",
"note": "history from February",
"entity_id": "ent_2",
}

@patch("robosystems_client.clients.ledger_client.op_record_statement_balance")
@patch("robosystems_client.clients.ledger_client.op_refresh_reconciliations")
@patch("robosystems_client.clients.ledger_client.op_preview_reconciliations")
def test_reconciliation_calls_send_entity_id(
self, mock_preview, mock_refresh, mock_record, mock_config, graph_id
):
mock_preview.return_value = _mock_response(
_envelope("preview-reconciliations", {"period": "2026-08", "rows": []})
)
mock_refresh.return_value = _mock_response(
_envelope("refresh-reconciliations", {"period": "2026-08", "reconciliations": []})
)
mock_record.return_value = _mock_response(
_envelope("record-statement-balance", _RECONCILIATION)
)
client = LedgerClient(mock_config)

client.preview_reconciliations(graph_id, "2026-08", entity_id="ent_2")
client.refresh_reconciliations(graph_id, "2026-08", entity_id="ent_2")
client.record_statement_balance(
graph_id,
element_id="elem_cash",
as_of="2026-08-31",
balance=1200.0,
entity_id="ent_2",
)

for mock in (mock_preview, mock_refresh, mock_record):
assert mock.call_args.kwargs["body"].entity_id == "ent_2"

@patch("robosystems_client.clients.ledger_client.op_reopen_period")
def test_reopen_period_sends_entity_id(self, mock_op, mock_config, graph_id):
mock_op.return_value = _mock_response(_envelope("reopen-period", {}))
Expand Down
Loading