diff --git a/robosystems_client/clients/ledger_client.py b/robosystems_client/clients/ledger_client.py index c5cb7c4..5dad74b 100644 --- a/robosystems_client/clients/ledger_client.py +++ b/robosystems_client/clients/ledger_client.py @@ -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, @@ -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") @@ -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, @@ -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() @@ -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, @@ -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( @@ -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). @@ -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. @@ -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, @@ -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: @@ -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() diff --git a/tests/test_ledger_client.py b/tests/test_ledger_client.py index 7ee2ef8..c65b767 100644 --- a/tests/test_ledger_client.py +++ b/tests/test_ledger_client.py @@ -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" @@ -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"