From 9b5fd4cad34eba20e8e8dd85397b513b01bca375 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Wed, 14 Oct 2026 19:34:36 -0500 Subject: [PATCH] feat(ledger): a journal entry line's cash flow row, and the in-place re-tag Regenerated against robosystems #1812. UpdateJournalEntryRequest gains line_flows; the line input and response carry flow_qname; the journal list selects flowQname. --- .../update_journal_entry.py | 76 +++++++++++------ robosystems_client/clients/ledger_client.py | 14 ++- .../generated/list_ledger_journal_entries.py | 1 + .../graphql/generated/operations.py | 1 + .../ledger/ListLedgerJournalEntries.graphql | 2 +- robosystems_client/graphql/schema.graphql | 1 + robosystems_client/models/__init__.py | 2 + .../models/journal_entry_line_flow.py | 85 +++++++++++++++++++ .../models/journal_entry_line_item_input.py | 24 ++++++ .../journal_entry_line_item_response.py | 21 +++++ .../models/update_journal_entry_request.py | 54 +++++++++++- tests/test_ledger_client.py | 27 ++++++ 12 files changed, 276 insertions(+), 32 deletions(-) create mode 100644 robosystems_client/models/journal_entry_line_flow.py diff --git a/robosystems_client/api/robo_ledger_ledger_events/update_journal_entry.py b/robosystems_client/api/robo_ledger_ledger_events/update_journal_entry.py index 6bc857f2..d6492cdc 100644 --- a/robosystems_client/api/robo_ledger_ledger_events/update_journal_entry.py +++ b/robosystems_client/api/robo_ledger_ledger_events/update_journal_entry.py @@ -113,10 +113,12 @@ def sync_detailed( ) -> Response[ErrorResponse | OperationEnvelopeJournalEntryResponse]: """Update Journal Entry - Update a draft journal entry. Posted entries are immutable and must be corrected via `create-event- - block(event_type='journal_entry_reversed')`. If line_items is provided, existing line items are - replaced atomically, the new set must balance, and a line on a retired (`is_active=false`) account - is refused. + Update a draft journal entry, or re-tag any entry's flows. If line_items is provided, existing line + items are replaced atomically, the new set must balance, and a line on a retired (`is_active=false`) + account is refused. `line_flows` sets the cash flow row of the named lines in place, on a posted + entry too, closed month included, since a flow moves no balance; the lines on the other side of the + entry follow when that side carries one flow. Posted entries accept nothing else and are corrected + via `create-event-block(event_type='journal_entry_reversed')`. **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. @@ -124,9 +126,12 @@ def sync_detailed( Args: graph_id (str): idempotency_key (None | str | Unset): - body (UpdateJournalEntryRequest): Update a draft journal entry. Posted entries cannot be - updated — - create a reversal and a corrected entry instead. + body (UpdateJournalEntryRequest): Update a draft journal entry, or re-tag any entry's + flows. + + Posted entries accept only `line_flows`: a flow moves no balance, so it + changes in place, closed month or not. Anything else on a posted entry is + a reversal and a corrected entry. Omitted fields are left unchanged. If `line_items` is provided, the existing line items are replaced entirely (atomic delete + insert) @@ -163,10 +168,12 @@ def sync( ) -> ErrorResponse | OperationEnvelopeJournalEntryResponse | None: """Update Journal Entry - Update a draft journal entry. Posted entries are immutable and must be corrected via `create-event- - block(event_type='journal_entry_reversed')`. If line_items is provided, existing line items are - replaced atomically, the new set must balance, and a line on a retired (`is_active=false`) account - is refused. + Update a draft journal entry, or re-tag any entry's flows. If line_items is provided, existing line + items are replaced atomically, the new set must balance, and a line on a retired (`is_active=false`) + account is refused. `line_flows` sets the cash flow row of the named lines in place, on a posted + entry too, closed month included, since a flow moves no balance; the lines on the other side of the + entry follow when that side carries one flow. Posted entries accept nothing else and are corrected + via `create-event-block(event_type='journal_entry_reversed')`. **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. @@ -174,9 +181,12 @@ def sync( Args: graph_id (str): idempotency_key (None | str | Unset): - body (UpdateJournalEntryRequest): Update a draft journal entry. Posted entries cannot be - updated — - create a reversal and a corrected entry instead. + body (UpdateJournalEntryRequest): Update a draft journal entry, or re-tag any entry's + flows. + + Posted entries accept only `line_flows`: a flow moves no balance, so it + changes in place, closed month or not. Anything else on a posted entry is + a reversal and a corrected entry. Omitted fields are left unchanged. If `line_items` is provided, the existing line items are replaced entirely (atomic delete + insert) @@ -208,10 +218,12 @@ async def asyncio_detailed( ) -> Response[ErrorResponse | OperationEnvelopeJournalEntryResponse]: """Update Journal Entry - Update a draft journal entry. Posted entries are immutable and must be corrected via `create-event- - block(event_type='journal_entry_reversed')`. If line_items is provided, existing line items are - replaced atomically, the new set must balance, and a line on a retired (`is_active=false`) account - is refused. + Update a draft journal entry, or re-tag any entry's flows. If line_items is provided, existing line + items are replaced atomically, the new set must balance, and a line on a retired (`is_active=false`) + account is refused. `line_flows` sets the cash flow row of the named lines in place, on a posted + entry too, closed month included, since a flow moves no balance; the lines on the other side of the + entry follow when that side carries one flow. Posted entries accept nothing else and are corrected + via `create-event-block(event_type='journal_entry_reversed')`. **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. @@ -219,9 +231,12 @@ async def asyncio_detailed( Args: graph_id (str): idempotency_key (None | str | Unset): - body (UpdateJournalEntryRequest): Update a draft journal entry. Posted entries cannot be - updated — - create a reversal and a corrected entry instead. + body (UpdateJournalEntryRequest): Update a draft journal entry, or re-tag any entry's + flows. + + Posted entries accept only `line_flows`: a flow moves no balance, so it + changes in place, closed month or not. Anything else on a posted entry is + a reversal and a corrected entry. Omitted fields are left unchanged. If `line_items` is provided, the existing line items are replaced entirely (atomic delete + insert) @@ -256,10 +271,12 @@ async def asyncio( ) -> ErrorResponse | OperationEnvelopeJournalEntryResponse | None: """Update Journal Entry - Update a draft journal entry. Posted entries are immutable and must be corrected via `create-event- - block(event_type='journal_entry_reversed')`. If line_items is provided, existing line items are - replaced atomically, the new set must balance, and a line on a retired (`is_active=false`) account - is refused. + Update a draft journal entry, or re-tag any entry's flows. If line_items is provided, existing line + items are replaced atomically, the new set must balance, and a line on a retired (`is_active=false`) + account is refused. `line_flows` sets the cash flow row of the named lines in place, on a posted + entry too, closed month included, since a flow moves no balance; the lines on the other side of the + entry follow when that side carries one flow. Posted entries accept nothing else and are corrected + via `create-event-block(event_type='journal_entry_reversed')`. **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. @@ -267,9 +284,12 @@ async def asyncio( Args: graph_id (str): idempotency_key (None | str | Unset): - body (UpdateJournalEntryRequest): Update a draft journal entry. Posted entries cannot be - updated — - create a reversal and a corrected entry instead. + body (UpdateJournalEntryRequest): Update a draft journal entry, or re-tag any entry's + flows. + + Posted entries accept only `line_flows`: a flow moves no balance, so it + changes in place, closed month or not. Anything else on a posted entry is + a reversal and a corrected entry. Omitted fields are left unchanged. If `line_items` is provided, the existing line items are replaced entirely (atomic delete + insert) diff --git a/robosystems_client/clients/ledger_client.py b/robosystems_client/clients/ledger_client.py index 711882e1..a144d4c5 100644 --- a/robosystems_client/clients/ledger_client.py +++ b/robosystems_client/clients/ledger_client.py @@ -1992,6 +1992,12 @@ def create_journal_entry( ``entity_id`` names the entity whose books the entry goes into; omitted, the group parent's. + A line dict may carry ``flow_qname``, the cash flow row its cash belongs + to (a ``qname`` from ``get_flow_candidates``): only for a loan drawn or + repaid, capital put in or taken out, an asset bought or sold. A flow on + one side of the entry tags the untagged lines on the other; one that is + not a row of the entity's statement is refused. + Returns the EventBlockEnvelope (event row fields). """ metadata: dict[str, Any] = { @@ -2024,7 +2030,13 @@ def create_journal_entry( def update_journal_entry( self, graph_id: str, body: dict[str, Any] ) -> JournalEntryResponse: - """Update a draft journal entry. Posted entries are immutable.""" + """Update a draft journal entry, or re-tag any entry's flows. + + ``line_flows`` (``[{"line_id", "flow_qname"}]``) sets the cash flow row + of the named lines in place, on a posted entry too, closed month + included, since a flow moves no balance; the lines on the other side of + the entry follow. It is the only change a posted entry accepts. + """ request = UpdateJournalEntryRequest.from_dict(body) response = op_update_journal_entry( graph_id=graph_id, body=request, client=self._get_client() diff --git a/robosystems_client/graphql/generated/list_ledger_journal_entries.py b/robosystems_client/graphql/generated/list_ledger_journal_entries.py index a3ea05d3..fe038633 100644 --- a/robosystems_client/graphql/generated/list_ledger_journal_entries.py +++ b/robosystems_client/graphql/generated/list_ledger_journal_entries.py @@ -47,6 +47,7 @@ class ListLedgerJournalEntriesJournalEntriesEntriesLineItems(BaseModel): credit_amount: float = Field(alias="creditAmount") description: Optional[str] line_order: int = Field(alias="lineOrder") + flow_qname: Optional[str] = Field(alias="flowQname") class ListLedgerJournalEntriesJournalEntriesPagination(BaseModel): diff --git a/robosystems_client/graphql/generated/operations.py b/robosystems_client/graphql/generated/operations.py index ddf0b133..20c14e95 100644 --- a/robosystems_client/graphql/generated/operations.py +++ b/robosystems_client/graphql/generated/operations.py @@ -1659,6 +1659,7 @@ creditAmount description lineOrder + flowQname } } pagination { diff --git a/robosystems_client/graphql/operations/ledger/ListLedgerJournalEntries.graphql b/robosystems_client/graphql/operations/ledger/ListLedgerJournalEntries.graphql index ad36a557..ae310bc8 100644 --- a/robosystems_client/graphql/operations/ledger/ListLedgerJournalEntries.graphql +++ b/robosystems_client/graphql/operations/ledger/ListLedgerJournalEntries.graphql @@ -27,7 +27,7 @@ query ListLedgerJournalEntries( totalDebit totalCredit balanced lineItems { id accountId accountName accountCode - debitAmount creditAmount description lineOrder + debitAmount creditAmount description lineOrder flowQname } } pagination { total limit offset hasMore } diff --git a/robosystems_client/graphql/schema.graphql b/robosystems_client/graphql/schema.graphql index 54ea9a7d..a20a118a 100644 --- a/robosystems_client/graphql/schema.graphql +++ b/robosystems_client/graphql/schema.graphql @@ -2257,6 +2257,7 @@ type LedgerLineItem { creditAmount: Float! description: String lineOrder: Int! + flowQname: String } """ diff --git a/robosystems_client/models/__init__.py b/robosystems_client/models/__init__.py index 3f472475..6459d33e 100644 --- a/robosystems_client/models/__init__.py +++ b/robosystems_client/models/__init__.py @@ -375,6 +375,7 @@ from .invoice import Invoice from .invoice_line_item import InvoiceLineItem from .invoices_response import InvoicesResponse +from .journal_entry_line_flow import JournalEntryLineFlow from .journal_entry_line_item_input import JournalEntryLineItemInput from .journal_entry_line_item_input_metadata_type_0 import ( JournalEntryLineItemInputMetadataType0, @@ -1460,6 +1461,7 @@ "Invoice", "InvoiceLineItem", "InvoicesResponse", + "JournalEntryLineFlow", "JournalEntryLineItemInput", "JournalEntryLineItemInputMetadataType0", "JournalEntryLineItemResponse", diff --git a/robosystems_client/models/journal_entry_line_flow.py b/robosystems_client/models/journal_entry_line_flow.py new file mode 100644 index 00000000..0694ce27 --- /dev/null +++ b/robosystems_client/models/journal_entry_line_flow.py @@ -0,0 +1,85 @@ +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="JournalEntryLineFlow") + + +@_attrs_define +class JournalEntryLineFlow: + """A line's cash flow row, set in place. + + Attributes: + line_id (str): The line to re-tag, by id. + flow_qname (None | str | Unset): The rs-gaap flow qname the line takes; null clears it, back to the account's + default flow. + """ + + line_id: str + flow_qname: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + line_id = self.line_id + + flow_qname: None | str | Unset + if isinstance(self.flow_qname, Unset): + flow_qname = UNSET + else: + flow_qname = self.flow_qname + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "line_id": line_id, + } + ) + if flow_qname is not UNSET: + field_dict["flow_qname"] = flow_qname + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + line_id = d.pop("line_id") + + def _parse_flow_qname(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + flow_qname = _parse_flow_qname(d.pop("flow_qname", UNSET)) + + journal_entry_line_flow = cls( + line_id=line_id, + flow_qname=flow_qname, + ) + + journal_entry_line_flow.additional_properties = d + return journal_entry_line_flow + + @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/journal_entry_line_item_input.py b/robosystems_client/models/journal_entry_line_item_input.py index 907634e8..9f89946d 100644 --- a/robosystems_client/models/journal_entry_line_item_input.py +++ b/robosystems_client/models/journal_entry_line_item_input.py @@ -29,6 +29,11 @@ class JournalEntryLineItemInput: debit_amount (int | Unset): Debit amount in cents. Must be 0 if `credit_amount` > 0. Default: 0. credit_amount (int | Unset): Credit amount in cents. Must be 0 if `debit_amount` > 0. Default: 0. description (None | str | Unset): Per-line memo (overrides the entry-level memo on this line). + flow_qname (None | str | Unset): The cash flow row this line's cash belongs to, an rs-gaap flow qname + (`flowCandidates(entityId)` lists the rows this company's statement offers). Only for a loan drawn or repaid, + capital put in or taken out, an asset bought or sold; omitted, the account's default flow applies. A flow set on + one side of the entry tags the untagged lines on the other: the cash flow statement reads the cash line and the + roll-forwards read the account line. Refused when it is not a row of the entity's cash flow statement. metadata (JournalEntryLineItemInputMetadataType0 | None | Unset): Optional per-line metadata stamped on `LineItem.metadata_`. Used to carry source-system fields the standard columns don't cover — e.g. an external flow-tag code that drives rollforward attribution (`transaction_description_code`), an external memo, or a cost- @@ -40,6 +45,7 @@ class JournalEntryLineItemInput: debit_amount: int | Unset = 0 credit_amount: int | Unset = 0 description: None | str | Unset = UNSET + flow_qname: None | str | Unset = UNSET metadata: JournalEntryLineItemInputMetadataType0 | None | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -60,6 +66,12 @@ def to_dict(self) -> dict[str, Any]: else: description = self.description + flow_qname: None | str | Unset + if isinstance(self.flow_qname, Unset): + flow_qname = UNSET + else: + flow_qname = self.flow_qname + metadata: dict[str, Any] | None | Unset if isinstance(self.metadata, Unset): metadata = UNSET @@ -81,6 +93,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["credit_amount"] = credit_amount if description is not UNSET: field_dict["description"] = description + if flow_qname is not UNSET: + field_dict["flow_qname"] = flow_qname if metadata is not UNSET: field_dict["metadata"] = metadata @@ -108,6 +122,15 @@ def _parse_description(data: object) -> None | str | Unset: description = _parse_description(d.pop("description", UNSET)) + def _parse_flow_qname(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + flow_qname = _parse_flow_qname(d.pop("flow_qname", UNSET)) + def _parse_metadata( data: object, ) -> JournalEntryLineItemInputMetadataType0 | None | Unset: @@ -132,6 +155,7 @@ def _parse_metadata( debit_amount=debit_amount, credit_amount=credit_amount, description=description, + flow_qname=flow_qname, metadata=metadata, ) diff --git a/robosystems_client/models/journal_entry_line_item_response.py b/robosystems_client/models/journal_entry_line_item_response.py index 9bb4909f..8b530189 100644 --- a/robosystems_client/models/journal_entry_line_item_response.py +++ b/robosystems_client/models/journal_entry_line_item_response.py @@ -22,6 +22,8 @@ class JournalEntryLineItemResponse: credit_amount (int): line_order (int): description (None | str | Unset): + flow_qname (None | str | Unset): The cash flow row this line carries, when tagged; null means the account's + default flow. """ id: str @@ -30,6 +32,7 @@ class JournalEntryLineItemResponse: credit_amount: int line_order: int description: None | str | Unset = UNSET + flow_qname: None | str | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -49,6 +52,12 @@ def to_dict(self) -> dict[str, Any]: else: description = self.description + flow_qname: None | str | Unset + if isinstance(self.flow_qname, Unset): + flow_qname = UNSET + else: + flow_qname = self.flow_qname + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -62,6 +71,8 @@ def to_dict(self) -> dict[str, Any]: ) if description is not UNSET: field_dict["description"] = description + if flow_qname is not UNSET: + field_dict["flow_qname"] = flow_qname return field_dict @@ -87,6 +98,15 @@ def _parse_description(data: object) -> None | str | Unset: description = _parse_description(d.pop("description", UNSET)) + def _parse_flow_qname(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + flow_qname = _parse_flow_qname(d.pop("flow_qname", UNSET)) + journal_entry_line_item_response = cls( id=id, element_id=element_id, @@ -94,6 +114,7 @@ def _parse_description(data: object) -> None | str | Unset: credit_amount=credit_amount, line_order=line_order, description=description, + flow_qname=flow_qname, ) journal_entry_line_item_response.additional_properties = d diff --git a/robosystems_client/models/update_journal_entry_request.py b/robosystems_client/models/update_journal_entry_request.py index cd116d5a..79eb0a36 100644 --- a/robosystems_client/models/update_journal_entry_request.py +++ b/robosystems_client/models/update_journal_entry_request.py @@ -13,6 +13,7 @@ from ..types import UNSET, Unset if TYPE_CHECKING: + from ..models.journal_entry_line_flow import JournalEntryLineFlow from ..models.journal_entry_line_item_input import JournalEntryLineItemInput @@ -21,8 +22,11 @@ @_attrs_define class UpdateJournalEntryRequest: - """Update a draft journal entry. Posted entries cannot be updated — - create a reversal and a corrected entry instead. + """Update a draft journal entry, or re-tag any entry's flows. + + Posted entries accept only `line_flows`: a flow moves no balance, so it + changes in place, closed month or not. Anything else on a posted entry is + a reversal and a corrected entry. Omitted fields are left unchanged. If `line_items` is provided, the existing line items are replaced entirely (atomic delete + insert) @@ -37,6 +41,11 @@ class UpdateJournalEntryRequest: by close-period. line_items (list[JournalEntryLineItemInput] | None | Unset): Replacement line items. Whole-list replacement (not patch). The new set must still balance (total_debit == total_credit). Omit to leave existing lines untouched. + line_flows (list[JournalEntryLineFlow] | None | Unset): Re-tag lines in place: each named line takes its + `flow_qname` (null clears it), and when every line on that side of the entry then carries one flow, the lines on + the other side not named here take it too, so the cash line and the account line agree. Allowed on a posted + entry, closed month included, and the only change one accepts; recorded on the entry as `flow_retags`. Not + combinable with `line_items`, which replaces the lines (set `flow_qname` on them). """ entry_id: str @@ -44,6 +53,7 @@ class UpdateJournalEntryRequest: memo: None | str | Unset = UNSET type_: None | Unset | UpdateJournalEntryRequestTypeType0 = UNSET line_items: list[JournalEntryLineItemInput] | None | Unset = UNSET + line_flows: list[JournalEntryLineFlow] | None | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -83,6 +93,18 @@ def to_dict(self) -> dict[str, Any]: else: line_items = self.line_items + line_flows: list[dict[str, Any]] | None | Unset + if isinstance(self.line_flows, Unset): + line_flows = UNSET + elif isinstance(self.line_flows, list): + line_flows = [] + for line_flows_type_0_item_data in self.line_flows: + line_flows_type_0_item = line_flows_type_0_item_data.to_dict() + line_flows.append(line_flows_type_0_item) + + else: + line_flows = self.line_flows + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -98,11 +120,14 @@ def to_dict(self) -> dict[str, Any]: field_dict["type"] = type_ if line_items is not UNSET: field_dict["line_items"] = line_items + if line_flows is not UNSET: + field_dict["line_flows"] = line_flows return field_dict @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.journal_entry_line_flow import JournalEntryLineFlow from ..models.journal_entry_line_item_input import JournalEntryLineItemInput d = dict(src_dict) @@ -177,12 +202,37 @@ def _parse_line_items( line_items = _parse_line_items(d.pop("line_items", UNSET)) + def _parse_line_flows(data: object) -> list[JournalEntryLineFlow] | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, list): + raise TypeError() + line_flows_type_0 = [] + _line_flows_type_0 = data + for line_flows_type_0_item_data in _line_flows_type_0: + line_flows_type_0_item = JournalEntryLineFlow.from_dict( + line_flows_type_0_item_data + ) + + line_flows_type_0.append(line_flows_type_0_item) + + return line_flows_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(list[JournalEntryLineFlow] | None | Unset, data) + + line_flows = _parse_line_flows(d.pop("line_flows", UNSET)) + update_journal_entry_request = cls( entry_id=entry_id, posting_date=posting_date, memo=memo, type_=type_, line_items=line_items, + line_flows=line_flows, ) update_journal_entry_request.additional_properties = d diff --git a/tests/test_ledger_client.py b/tests/test_ledger_client.py index 9767483e..20043ec5 100644 --- a/tests/test_ledger_client.py +++ b/tests/test_ledger_client.py @@ -841,6 +841,32 @@ def test_update_journal_entry(self, mock_op, mock_config, graph_id): assert result["memo"] == "Updated memo" assert mock_op.call_args.kwargs["graph_id"] == graph_id + @patch("robosystems_client.clients.ledger_client.op_update_journal_entry") + def test_update_journal_entry_re_tags_lines_in_place( + self, mock_op, mock_config, graph_id + ): + flow = "rs-gaap:ProceedsFromIssuanceOfLongTermDebt" + envelope = _envelope( + "update-journal-entry", + { + "id": "je_1", + "status": "posted", + "line_items": [ + {"id": "li_1", "flow_qname": flow}, + {"id": "li_2", "flow_qname": flow}, + ], + }, + ) + mock_op.return_value = _mock_response(envelope) + client = LedgerClient(mock_config) + result = client.update_journal_entry( + graph_id, + {"entry_id": "je_1", "line_flows": [{"line_id": "li_2", "flow_qname": flow}]}, + ) + sent = mock_op.call_args.kwargs["body"] + assert [(f.line_id, f.flow_qname) for f in sent.line_flows] == [("li_2", flow)] + assert [li["flow_qname"] for li in result["line_items"]] == [flow, flow] + @patch("robosystems_client.clients.ledger_client.op_delete_journal_entry") def test_delete_journal_entry_returns_deleted_sentinel( self, mock_op, mock_config, graph_id @@ -1097,6 +1123,7 @@ def test_list_journal_entries_returns_standalone_entries( "creditAmount": 0.0, "description": None, "lineOrder": 1, + "flowQname": None, } ], }