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
27 changes: 23 additions & 4 deletions robosystems_client/clients/ledger_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ def _build_event_block_request(
event_category: str,
occurred_at: str,
metadata: dict[str, Any],
source: str = "native",
source: str = "manual",
event_class: str = "economic",
obligated_by_event_id: str | None = None,
discharges_event_id: str | None = None,
Expand All @@ -448,6 +448,12 @@ def _build_event_block_request(

``occurred_at`` accepts either a date string (``YYYY-MM-DD``) — which
is normalized to midnight UTC — or a full ISO-8601 timestamp.

``source`` describes who fired the event — must match the server's
CHECK constraint set: ``manual`` (user-initiated, the default),
``schedule`` (recurring schedule fired), ``system`` (internal
automation), or one of the adapter-driven values
(``quickbooks`` / ``xero`` / ``plaid``) for sync ingestion.
"""
if "T" not in occurred_at:
occurred_dt = datetime.datetime.fromisoformat(f"{occurred_at}T00:00:00+00:00")
Expand Down Expand Up @@ -985,12 +991,14 @@ def dispose_schedule(
sale_proceeds: int | None = None,
proceeds_element_id: str | None = None,
gain_loss_element_id: str | None = None,
source: str = "manual",
) -> EventBlockEnvelope:
"""Dispose of a schedule asset — atomically truncates forward facts,
drops the SumEquals rule, and posts a balanced disposal entry.

Routes through ``create-event-block`` with
``event_type='asset_disposed'``.
``event_type='asset_disposed'``. ``source`` defaults to ``"manual"``
(user-initiated disposal); sync adapters override.
"""
metadata: dict[str, Any] = {
"schedule_id": structure_id,
Expand All @@ -1008,6 +1016,7 @@ def dispose_schedule(
event_category="adjustment",
occurred_at=disposal_date,
metadata=metadata,
source=source,
)
response = op_create_event_block(
graph_id=graph_id, body=body, client=self._get_client()
Expand Down Expand Up @@ -1099,7 +1108,8 @@ def create_closing_entry(
Routes through ``create-event-block`` with
``event_type='schedule_entry_due'`` — the underlying handler dispatches
one of created / unchanged / regenerated / removed / skipped internally.
Returns the EventBlockEnvelope.
Always emits ``source='schedule'`` since the event is schedule-driven
by definition. Returns the EventBlockEnvelope.
"""
metadata: dict[str, Any] = {
"schedule_id": structure_id,
Expand All @@ -1113,7 +1123,7 @@ def create_closing_entry(
event_type="schedule_entry_due",
event_category="recognition",
occurred_at=posting_date,
source="scheduled",
source="schedule",
metadata=metadata,
)
response = op_create_event_block(
Expand All @@ -1134,6 +1144,7 @@ def create_journal_entry(
type: str = "standard", # noqa: A002
status: str = "draft",
transaction_id: str | None = None,
source: str = "manual",
idempotency_key: str | None = None,
) -> EventBlockEnvelope:
"""Create a journal entry with balanced line items (DR=CR enforced).
Expand All @@ -1146,6 +1157,10 @@ def create_journal_entry(
``status='posted'`` for historical data import where entries
represent already-happened business events.

``source`` defaults to ``"manual"`` (user-initiated). Sync adapters
(QuickBooks, Plaid, etc.) pass their adapter name so the underlying
Event row records the correct origin.

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.
Expand All @@ -1166,6 +1181,7 @@ def create_journal_entry(
event_category="adjustment",
occurred_at=posting_date,
metadata=metadata,
source=source,
)
response = op_create_event_block(
graph_id=graph_id,
Expand Down Expand Up @@ -1203,11 +1219,13 @@ def reverse_journal_entry(
posting_date: str | None = None,
memo: str | None = None,
reason: str | None = None,
source: str = "manual",
) -> EventBlockEnvelope:
"""Reverse a posted journal entry (creates offsetting entry, marks original as reversed).

Routes through ``create-event-block`` with
``event_type='journal_entry_reversed'``. Returns the EventBlockEnvelope.
``source`` defaults to ``"manual"`` — sync adapters override.
"""
metadata: dict[str, Any] = {"entry_id": entry_id}
if posting_date is not None:
Expand All @@ -1222,6 +1240,7 @@ def reverse_journal_entry(
event_category="adjustment",
occurred_at=occurred_at,
metadata=metadata,
source=source,
)
response = op_create_event_block(
graph_id=graph_id, body=body, client=self._get_client()
Expand Down
4 changes: 2 additions & 2 deletions tests/test_ledger_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ def test_create_closing_entry(self, mock_op, mock_config, graph_id):
body = mock_op.call_args.kwargs["body"]
assert body.event_type == "schedule_entry_due"
assert body.event_category.value == "recognition"
assert body.source == "scheduled"
assert body.source == "schedule"
assert body.apply_handlers is True
metadata = body.metadata.to_dict()
assert metadata["schedule_id"] == "str_1"
Expand Down Expand Up @@ -442,7 +442,7 @@ def test_create_journal_entry_basic(self, mock_op, mock_config, graph_id):
body = call_kwargs["body"]
assert body.event_type == "journal_entry_recorded"
assert body.event_category.value == "adjustment"
assert body.source == "native"
assert body.source == "manual"
assert body.apply_handlers is True
metadata = body.metadata.to_dict()
assert metadata["memo"] == "Q1 revenue"
Expand Down
Loading