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
81 changes: 80 additions & 1 deletion robosystems_client/clients/ledger_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from dataclasses import dataclass
from http import HTTPStatus
from pathlib import Path
from typing import Any
from typing import Any, Literal

import httpx

Expand Down Expand Up @@ -468,6 +468,19 @@
ClassificationStats,
ClassificationStatsClassificationStats,
)
from ..graphql.generated.aged_payables import AgedPayables, AgedPayablesAgedPayables
from ..graphql.generated.aged_receivables import (
AgedReceivables,
AgedReceivablesAgedReceivables,
)
from ..graphql.generated.discharge_candidates import (
DischargeCandidates,
DischargeCandidatesDischargeCandidates,
)
from ..graphql.generated.open_items_for_agent import (
OpenItemsForAgent,
OpenItemsForAgentOpenItemsForAgent,
)
from ..graphql.generated.operations import (
GET_INFORMATION_BLOCK_GQL,
GET_LEDGER_ACCOUNT_ROLLUPS_GQL,
Expand Down Expand Up @@ -513,6 +526,10 @@
MAPPING_CANDIDATES_GQL,
FLOW_CANDIDATES_GQL,
CLASSIFICATION_STATS_GQL,
AGED_RECEIVABLES_GQL,
AGED_PAYABLES_GQL,
OPEN_ITEMS_FOR_AGENT_GQL,
DISCHARGE_CANDIDATES_GQL,
)
from ..models.add_publish_list_members_operation import AddPublishListMembersOperation
from ..models.auto_map_elements_operation import AutoMapElementsOperation
Expand Down Expand Up @@ -2424,6 +2441,68 @@ def get_classification_stats(
)
return ClassificationStats.model_validate(data).classification_stats

# ── Receivables and payables ──────────────────────────────────────────

def get_aged_balances(
self,
graph_id: str,
kind: Literal["receivables", "payables"],
*,
as_of: str | None = None,
entity_id: str | None = None,
scope: Literal["entity", "group"] = "entity",
) -> AgedReceivablesAgedReceivables | AgedPayablesAgedPayables:
"""Receivables or payables aged at a date (``as_of``, ``YYYY-MM-DD``,
default today on the server): totals by days past due, a row per
counterparty, and each entity's open items tied out to its receivables
or payables account (``difference_cents``, never plugged). ``scope`` is
``entity`` (one entity's books, ``entity_id`` default the group parent)
or ``group`` (the parent and its members consolidated in full, with what
they owe each other in ``intercompany`` and ``pairs``). An item with no
due date of its own is due on receipt and counted in
``defaulted_due_date_count``. Both kinds return the same fields.
"""
variables = {"asOf": as_of, "entityId": entity_id, "scope": scope}
if kind == "receivables":
data = self._query(graph_id, AGED_RECEIVABLES_GQL, variables)
return AgedReceivables.model_validate(data).aged_receivables
data = self._query(graph_id, AGED_PAYABLES_GQL, variables)
return AgedPayables.model_validate(data).aged_payables

def get_open_items_for_agent(
self,
graph_id: str,
agent_id: str,
kind: Literal["receivables", "payables"],
*,
as_of: str | None = None,
entity_id: str | None = None,
) -> list[OpenItemsForAgentOpenItemsForAgent]:
"""One counterparty's open invoices (``receivables``) or bills
(``payables``) at a date, oldest first, each with its due date, days
past due and bucket: the drill from an aged row into its documents.
"""
data = self._query(
graph_id,
OPEN_ITEMS_FOR_AGENT_GQL,
{"agentId": agent_id, "kind": kind, "asOf": as_of, "entityId": entity_id},
)
return list(OpenItemsForAgent.model_validate(data).open_items_for_agent)

def get_discharge_candidates(
self, graph_id: str, event_id: str
) -> DischargeCandidatesDischargeCandidates | None:
"""The open invoices or bills a payment or bank line may settle: its own
entity's and counterparty's, open at its date, an exact amount match
first, then the oldest due date. ``classified_to_control`` says the line
went to a receivables or payables account, where a pick is expected.
Record the choice with ``discharge_allocations`` on
``update_event_block``; nothing settles without one. ``None`` when there
is no such event.
"""
data = self._query(graph_id, DISCHARGE_CANDIDATES_GQL, {"eventId": event_id})
return DischargeCandidates.model_validate(data).discharge_candidates

# ── Event handlers (DSL handler registry) ────────────────────────────

def create_event_handler(
Expand Down
59 changes: 59 additions & 0 deletions robosystems_client/graphql/generated/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
from .aged_payables import (
AgedPayables,
AgedPayablesAgedPayables,
AgedPayablesAgedPayablesAgents,
AgedPayablesAgedPayablesAgentsBuckets,
AgedPayablesAgedPayablesBuckets,
AgedPayablesAgedPayablesIntercompany,
AgedPayablesAgedPayablesPairs,
AgedPayablesAgedPayablesTieOuts,
AgedPayablesAgedPayablesTieOutsAccounts,
)
from .aged_receivables import (
AgedReceivables,
AgedReceivablesAgedReceivables,
AgedReceivablesAgedReceivablesAgents,
AgedReceivablesAgedReceivablesAgentsBuckets,
AgedReceivablesAgedReceivablesBuckets,
AgedReceivablesAgedReceivablesIntercompany,
AgedReceivablesAgedReceivablesPairs,
AgedReceivablesAgedReceivablesTieOuts,
AgedReceivablesAgedReceivablesTieOutsAccounts,
)
from .base_client import BaseClient
from .base_model import BaseModel, Upload
from .classification_stats import (
Expand All @@ -6,6 +28,11 @@
ClassificationStatsClassificationStatsBySource,
)
from .client import Client
from .discharge_candidates import (
DischargeCandidates,
DischargeCandidatesDischargeCandidates,
DischargeCandidatesDischargeCandidatesCandidates,
)
from .enums import ReportDownloadFormat, ReportLifecycle
from .exceptions import (
GraphQLClientError,
Expand Down Expand Up @@ -351,8 +378,12 @@
ListLibraryTaxonomyArcsLibraryTaxonomyArcs,
)
from .mapping_candidates import MappingCandidates, MappingCandidatesMappingCandidates
from .open_items_for_agent import OpenItemsForAgent, OpenItemsForAgentOpenItemsForAgent
from .operations import (
AGED_PAYABLES_GQL,
AGED_RECEIVABLES_GQL,
CLASSIFICATION_STATS_GQL,
DISCHARGE_CANDIDATES_GQL,
FLOW_CANDIDATES_GQL,
GET_INFORMATION_BLOCK_GQL,
GET_INVESTOR_HOLDINGS_GQL,
Expand Down Expand Up @@ -412,6 +443,7 @@
LIST_LIBRARY_TAXONOMIES_GQL,
LIST_LIBRARY_TAXONOMY_ARCS_GQL,
MAPPING_CANDIDATES_GQL,
OPEN_ITEMS_FOR_AGENT_GQL,
SEARCH_LIBRARY_ELEMENTS_GQL,
)
from .search_library_elements import (
Expand All @@ -422,13 +454,37 @@
)

__all__ = [
"AGED_PAYABLES_GQL",
"AGED_RECEIVABLES_GQL",
"AgedPayables",
"AgedPayablesAgedPayables",
"AgedPayablesAgedPayablesAgents",
"AgedPayablesAgedPayablesAgentsBuckets",
"AgedPayablesAgedPayablesBuckets",
"AgedPayablesAgedPayablesIntercompany",
"AgedPayablesAgedPayablesPairs",
"AgedPayablesAgedPayablesTieOuts",
"AgedPayablesAgedPayablesTieOutsAccounts",
"AgedReceivables",
"AgedReceivablesAgedReceivables",
"AgedReceivablesAgedReceivablesAgents",
"AgedReceivablesAgedReceivablesAgentsBuckets",
"AgedReceivablesAgedReceivablesBuckets",
"AgedReceivablesAgedReceivablesIntercompany",
"AgedReceivablesAgedReceivablesPairs",
"AgedReceivablesAgedReceivablesTieOuts",
"AgedReceivablesAgedReceivablesTieOutsAccounts",
"BaseClient",
"BaseModel",
"CLASSIFICATION_STATS_GQL",
"ClassificationStats",
"ClassificationStatsClassificationStats",
"ClassificationStatsClassificationStatsBySource",
"Client",
"DISCHARGE_CANDIDATES_GQL",
"DischargeCandidates",
"DischargeCandidatesDischargeCandidates",
"DischargeCandidatesDischargeCandidatesCandidates",
"FLOW_CANDIDATES_GQL",
"FlowCandidates",
"FlowCandidatesFlowCandidates",
Expand Down Expand Up @@ -742,6 +798,9 @@
"MAPPING_CANDIDATES_GQL",
"MappingCandidates",
"MappingCandidatesMappingCandidates",
"OPEN_ITEMS_FOR_AGENT_GQL",
"OpenItemsForAgent",
"OpenItemsForAgentOpenItemsForAgent",
"ReportDownloadFormat",
"ReportLifecycle",
"SEARCH_LIBRARY_ELEMENTS_GQL",
Expand Down
96 changes: 96 additions & 0 deletions robosystems_client/graphql/generated/aged_payables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from typing import Optional

from pydantic import Field

from .base_model import BaseModel


class AgedPayables(BaseModel):
aged_payables: "AgedPayablesAgedPayables" = Field(alias="agedPayables")


class AgedPayablesAgedPayables(BaseModel):
kind: str
as_of: str = Field(alias="asOf")
scope: str
entity_id: Optional[str] = Field(alias="entityId")
member_entity_ids: list[str] = Field(alias="memberEntityIds")
buckets: "AgedPayablesAgedPayablesBuckets"
agents: list["AgedPayablesAgedPayablesAgents"]
defaulted_due_date_count: int = Field(alias="defaultedDueDateCount")
intercompany: list["AgedPayablesAgedPayablesIntercompany"]
pairs: list["AgedPayablesAgedPayablesPairs"]
tie_outs: list["AgedPayablesAgedPayablesTieOuts"] = Field(alias="tieOuts")


class AgedPayablesAgedPayablesBuckets(BaseModel):
current_cents: int = Field(alias="currentCents")
one_to_thirty_cents: int = Field(alias="oneToThirtyCents")
thirty_one_to_sixty_cents: int = Field(alias="thirtyOneToSixtyCents")
sixty_one_to_ninety_cents: int = Field(alias="sixtyOneToNinetyCents")
over_ninety_cents: int = Field(alias="overNinetyCents")
unapplied_credit_cents: int = Field(alias="unappliedCreditCents")
total_cents: int = Field(alias="totalCents")


class AgedPayablesAgedPayablesAgents(BaseModel):
agent_id: Optional[str] = Field(alias="agentId")
agent_name: Optional[str] = Field(alias="agentName")
open_item_count: int = Field(alias="openItemCount")
oldest_due_date: Optional[str] = Field(alias="oldestDueDate")
buckets: "AgedPayablesAgedPayablesAgentsBuckets"


class AgedPayablesAgedPayablesAgentsBuckets(BaseModel):
current_cents: int = Field(alias="currentCents")
one_to_thirty_cents: int = Field(alias="oneToThirtyCents")
thirty_one_to_sixty_cents: int = Field(alias="thirtyOneToSixtyCents")
sixty_one_to_ninety_cents: int = Field(alias="sixtyOneToNinetyCents")
over_ninety_cents: int = Field(alias="overNinetyCents")
unapplied_credit_cents: int = Field(alias="unappliedCreditCents")
total_cents: int = Field(alias="totalCents")


class AgedPayablesAgedPayablesIntercompany(BaseModel):
event_id: str = Field(alias="eventId")
entity_id: str = Field(alias="entityId")
agent_id: Optional[str] = Field(alias="agentId")
occurred_on: str = Field(alias="occurredOn")
document_number: Optional[str] = Field(alias="documentNumber")
description: Optional[str]
original_cents: int = Field(alias="originalCents")
settled_cents: int = Field(alias="settledCents")
open_cents: int = Field(alias="openCents")
currency: str
due_date: str = Field(alias="dueDate")
due_date_basis: str = Field(alias="dueDateBasis")
days_past_due: int = Field(alias="daysPastDue")
bucket: str


class AgedPayablesAgedPayablesPairs(BaseModel):
creditor_entity_id: str = Field(alias="creditorEntityId")
debtor_entity_id: str = Field(alias="debtorEntityId")
receivable_cents: int = Field(alias="receivableCents")
payable_cents: int = Field(alias="payableCents")
difference_cents: int = Field(alias="differenceCents")


class AgedPayablesAgedPayablesTieOuts(BaseModel):
entity_id: str = Field(alias="entityId")
accounts: list["AgedPayablesAgedPayablesTieOutsAccounts"]
control_balance_cents: Optional[int] = Field(alias="controlBalanceCents")
open_items_cents: int = Field(alias="openItemsCents")
difference_cents: Optional[int] = Field(alias="differenceCents")


class AgedPayablesAgedPayablesTieOutsAccounts(BaseModel):
element_id: str = Field(alias="elementId")
code: Optional[str]
name: str


AgedPayables.model_rebuild()
AgedPayablesAgedPayables.model_rebuild()
AgedPayablesAgedPayablesAgents.model_rebuild()
AgedPayablesAgedPayablesTieOuts.model_rebuild()
Loading
Loading