@accounter/server
Version:
Accounter GraphQL server
128 lines (127 loc) • 7.19 kB
TypeScript
import DataLoader from 'dataloader';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
import { FinancialAccountsProvider } from '../../financial-accounts/providers/financial-accounts.provider.js';
import { FinancialBankAccountsProvider } from '../../financial-accounts/providers/financial-bank-accounts.provider.js';
import { TransactionsProvider } from '../../transactions/providers/transactions.provider.js';
import { type MatchableTransaction } from '../helpers/match-security-executions.helper.js';
import type { ChargeSecurityProto, PaginatedSecurityExecutionsProto, SecurityExecutionRow, SecurityExecutionsFilterInput } from '../types.js';
import { SecurityBusinessesProvider } from './security-businesses.provider.js';
/**
* How many securities `includeCharges` will pair at once.
*
* Each one costs its whole execution history plus a transactions query, because the pairing is
* only correct over a complete set (see `matchExecutionsToTransactions`). The cap is what keeps
* "every trade I ever made, with charges" from turning into a portfolio-wide fan-out.
*/
export declare const MAX_CHARGE_LINK_SECURITIES = 10;
/** What the reverse match needs off a transaction, charge included so a row can link out. */
type MatchedTransaction = MatchableTransaction & {
charge_id: string;
};
/**
* A Poalim security key, qualified by the owner it belongs to.
*
* The bank's key is only unique within an owner, and reads follow the request's whole business
* scope rather than a single business — so the key alone cannot identify a security once a tenant
* has two businesses trading the same one.
*/
export type OwnedSecurityKey = {
ownerId: string;
securityKey: string;
};
export declare class ForeignSecuritiesProvider {
private db;
private transactionsProvider;
private financialAccountsProvider;
private financialBankAccountsProvider;
private securityBusinessesProvider;
constructor(db: TenantAwareDBClient, transactionsProvider: TransactionsProvider, financialAccountsProvider: FinancialAccountsProvider, financialBankAccountsProvider: FinancialBankAccountsProvider, securityBusinessesProvider: SecurityBusinessesProvider);
private batchSecuritiesByKeys;
securityByKeyLoader: DataLoader<OwnedSecurityKey, import("../types.js").IGetSecuritiesByKeysResult | null, string>;
/**
* The Poalim identity (bank/branch/account) of each account the given transactions touch.
* `financial_accounts.account_number` is text while the poalim_* tables store it as an
* integer, so non-numeric account numbers (and non-bank accounts, which have no
* financial_bank_accounts row) simply drop out — they can never match an execution.
*/
private getAccountTuples;
/**
* Ingested portfolio executions matched to the charge's transactions, grouped by security
* key. Returns an empty map when the charge touches no resolvable Poalim account, so a
* charge whose accounts predate the bank-account backfill degrades to "no activity" rather
* than erroring.
*/
private getMatchedExecutions;
/**
* The whole ingested life of one security business: every execution of every Poalim key it
* is known by, each carrying the cash movement (and so the charge) behind it.
*
* The candidate transactions are the security business's own — which is what the counterparty
* now is for a resolved trade — so this reads the same pairing the charge view shows, from
* the other end.
*/
getSecurityBusinessHistory(businessId: string, ownerId: string): Promise<{
executions: import("../types.js").IGetSecurityExecutionsByBusinessIdsResult[];
transactionByExecutionId: Map<string, MatchedTransaction>;
}>;
/**
* Every ingested execution of every security business the tenant has, grouped by the business
* it belongs to. Each business gets an entry, so "nothing ingested" is distinguishable from
* "not a security".
*
* One query for the whole portfolio: `getSecurityExecutionsByBusinessIds` joins the identifier
* bridge, so every business can be asked for at once and each row already knows which one it
* belongs to — including when two businesses trade the same security under the same Poalim key,
* which the key alone cannot tell apart.
*
* Unlike `getSecurityBusinessHistory` this never looks at transactions or accounts — those
* exist only to pair an execution with the cash movement behind it, which a position does not
* need, and they cost a transactions query plus an account lookup per security.
*/
getExecutionsBySecurityBusiness(): Promise<Map<string, SecurityExecutionRow[]>>;
/**
* Which securities a filter names.
*
* `securityBusinessIds`, `isins` and `symbols` are three ways of naming the same axis, so they
* union with each other rather than intersecting — asking for one ISIN and one symbol means
* both securities, not the empty overlap. Naming none of them means every security.
*
* Resolved against the request-memoized `getAllSecurityBusinesses()` rather than with three
* more queries: it is one round trip already paid for, and going through it means an id that
* is not a security business of this tenant resolves to nothing instead of reaching the
* executions feed.
*/
private resolveFilterSecurityBusinessIds;
/**
* A page of executions across securities, newest first.
*
* Two paths, because charge links and pagination do not compose. Without them the filter
* pushes straight into SQL and the page is a `LIMIT`/`OFFSET` slice. With them the pairing has
* to see a security's *whole* history — `matchExecutionsToTransactions` is greedy and
* one-to-one over the sets it is handed, so pairing a page's slice would let an execution on
* page 2 claim the cash movement that belongs to one on page 1, and the same execution would
* report a different charge at a different page size. So that path reuses
* `getSecurityBusinessHistory` per security, unpaginated, and slices in memory — which is why
* it is capped at {@link MAX_CHARGE_LINK_SECURITIES} securities.
*/
getSecurityExecutionsPage(params: {
filters: SecurityExecutionsFilterInput;
page: number;
limit: number;
includeCharges: boolean;
ownerId: string;
}): Promise<PaginatedSecurityExecutionsProto>;
/**
* The `includeCharges` path: every named security's complete history, paired, then filtered,
* ordered and sliced in memory. See {@link getSecurityExecutionsPage} for why it cannot be
* done in SQL.
*/
private chargeLinkedExecutionsPage;
/**
* The securities a charge's transactions reference, keyed off the security key each
* description carries. Keys with no ingested row are still returned, with a null
* `details`, so a stale or missing scrape is visible instead of silently dropping data.
*/
getChargeSecurities(chargeId: string, ownerId: string): Promise<ChargeSecurityProto[]>;
}
export {};