Skip to content

MngGetManagersCustomerStatsByFilter

Description: Returns one aggregate row per visible manager: how many leads and clients are assigned to that manager, and the deposit and withdrawal turnover of those customers' trading accounts, normalized to USD.

Aggregates are built from the in-memory staff, customer, account, and trade caches. Rows are emitted in the columnar structure + rows form used by MngGetAccountsByFilter and the dashboard commands, not in the object form used by GetManagers.

Manager visibility, brand scope, filtering, sorting, and pagination behave exactly as in MngGetManagersByFilter; the command delegates manager selection to the same code path. Brand scope is applied before filtering, sorting, pagination, and count. Hidden recovery manager id=1 never participates in filtering, totals, sorting, or pagination.

Customers are attributed to a manager through CustomerRecord.assigned_manager_id only. There is no fallback to manager_id. Statistics cover the entire history: the command takes no period arguments.

Access Control

🛡️ Access Level Required: SESSION_ADMIN

The router rejects any other session type before the handler runs. The handler then resolves the caller's manager access context and returns 403 when the manager cannot be resolved, is disabled, or does not hold a staff session.

  • Super admins (enable = 1 and super_admin = 1) receive every visible manager.
  • Other admins receive only managers of their own brand, excluding super admins. When the caller's brand is empty, the result is empty.
  • Recovery manager id=1 is excluded for everyone, unconditionally.

See also: Staff Access Model.


Request Parameters

All request parameters are optional and identical to MngGetManagersByFilter. If no filter is passed, the command returns all visible managers.

Field Type Description
access_backoffice int Shortcut filter. 1 returns managers with BackOffice access, 0 returns managers without it
access_crm int Shortcut filter. 1 returns managers with CRM access, 0 returns managers without it
limit int Optional page size. Range 1..50000
offset int Optional page offset. Minimum 0
where array Optional filter conditions in the same format as MngGetManagersByFilter: [field, operator, value]. All conditions must match
orWhere array Optional OR comparison group; at least one condition must match. Other filters stay mandatory
whereNot array Optional negative filter conditions in [field, value] pair form
whereIn array Optional inclusion filter conditions in [field, [values]] format
whereNotIn array Optional exclusion filter conditions in [field, [values]] format
whereBetween array Optional range filter conditions in [field, [from, to]] format
whereNotBetween array Optional negative range filter conditions
orderBy array Optional sorting. Supports ["field", "ASC"] / ["field", "DESC"] and the nested form [["field", "DESC"]]

Supported filter fields are the manager identity, profile, access scope, runtime state, and permission fields of ManagerRecord - the same set as MngGetManagersByFilter. The admin and super_admin fields can be used in where and orderBy expressions. Aggregate columns cannot; see Filtering And Sorting.

Common filter semantics, including grouped orWhere, are described in Table filter syntax.

Request Examples

All visible managers, stable order:

{
  "orderBy": ["id", "ASC"]
}

First page of enabled CRM managers:

{
  "access_crm": 1,
  "where": [
    ["enable", "=", 1]
  ],
  "orderBy": ["name", "ASC"],
  "limit": 20,
  "offset": 0
}

Name search. like is a case-insensitive substring match:

{
  "where": [
    ["name", "like", "ivan"]
  ]
}

Specific managers by id:

{
  "whereIn": [
    ["id", [2, 7, 9]]
  ],
  "orderBy": ["id", "ASC"]
}


Response Parameters

Field Type Description
structure array Ordered column names as strings. Clients must read values positionally against this array instead of relying on fixed indices
rows array One nested array per manager, values in structure order
count int Total number of rows matching the filter before pagination

Response Columns

Ten columns, in structure order. Money columns are always USD; counters are plain integers.

# Column Type Meaning
1 id int Manager id
2 name string Manager name
3 leads_count int Assigned customers with lifecycle_stage = 0 (LEAD)
4 clients_count int Assigned customers with lifecycle_stage greater than LEAD: REGISTERED, KYC_PENDING, KYC_APPROVED, FIRST_DEPOSIT, ACTIVE_TRADER, DORMANT
5 customers_count int leads_count + clients_count. The two sets are disjoint and exhaustive, so this always holds
6 deposits_amount double Sum of executed deposits across the assigned customers' accounts, USD, rounded to 2 decimals
7 withdrawals_amount double Sum of executed withdrawals, USD, rounded to 2 decimals. Always non-negative: the sign is stripped
8 net_deposit double deposits_amount - withdrawals_amount, USD, computed from the already rounded components
9 deposits_count int Number of executed deposit operations
10 withdrawals_count int Number of executed withdrawal operations

Lifecycle stage values are described in the Customers TCP API.

Response Example

Handler payload. The TCP transport wraps it in { extID, status, data }:

{
  "structure": [
    "id", "name",
    "leads_count", "clients_count", "customers_count",
    "deposits_amount", "withdrawals_amount", "net_deposit",
    "deposits_count", "withdrawals_count"
  ],
  "rows": [
    [7,  "Sales Manager", 12, 34, 46, 148320.50, 22910.75, 125409.75, 96, 31],
    [9,  "CRM Manager",    5,  8, 13,  21400.00,  3150.00,  18250.00, 14,  6],
    [11, "New Hire",       3,  0,  3,      0.00,     0.00,      0.00,  0,  0]
  ],
  "count": 3
}

Full TCP frame. The request must be terminated with \r\n:

{
  "command": "MngGetManagersCustomerStatsByFilter",
  "extID": "1",
  "__token": "<admin jwt>",
  "data": {
    "limit": 20,
    "offset": 0,
    "orderBy": ["id", "ASC"]
  }
}

How Values Are Computed

Attribution chain

Every aggregate is resolved along one chain. Every link is an exact-match join, without wildcards:

Step Join key Source
manager → customers CustomerRecord.assigned_manager_id Customer cache
customer → accounts AccountRecord.customer_id Account cache
account → currency AccountRecord.group → GroupRecord.currency Group cache
account → operations TradeRecord.login Trade cache

Which operations count as deposits and withdrawals

An operation is included only when all of the following hold:

  • cmd is OP_BALANCE_IN (6) or OP_BALANCE_OUT (8). Credit and bonus commands are excluded.
  • state is TS_CLOSED_NORMAL. Pending requests, declined requests, and deleted operations are excluded.
  • reason is a client reason. Internal platform movements are excluded: TR_REASON_PROP (8), TR_REASON_COPY_REWARD (10), and TR_REASON_BONUS_CONVERSION (17). Cashier refunds and chargebacks are counted: they are client money with a reversed sign.

The amount is taken from TradeRecord.profit, the value actually applied to the account balance. TradeRecord.close_price holds the requested amount and is not used. Field and enum definitions are listed in TradeRecord.

Currency normalization

Operations are aggregated per (manager, account currency) pair, then each currency total is converted once through the platform conversion rate for account_currency → USD in the OP_BUY direction. Aggregating before converting is exact, because a single current rate is applied to the whole sum.

USD accounts pass through the same branch and resolve to a rate of 1.0. The rate is the current market rate, not the rate at the time of the operation: balance operations do not carry a historical conversion rate.

Order of operations

  1. Exclude recovery manager id=1.
  2. Apply brand scope. Skipped for super admins.
  3. Apply the access_crm / access_backoffice shortcut filters.
  4. Apply the where family of conditions.
  5. Apply orderBy.
  6. Set count from the full matching set.
  7. Slice the page with limit / offset.
  8. Compute the aggregates for the managers on that page.

Filtering And Sorting

Filtering and sorting operate on ManagerRecord fields, resolved exactly as in MngGetManagersByFilter.

String fields: name, email, phone, country, city, address, position, language, brand, groups, desks, messengers, and social_networks. Operators =, ==, !=, and like. like is a case-insensitive substring match and strips % from the value, so "%ivan%" and "ivan" behave identically. Relational operators are not supported for strings.

Numeric, flag, and time fields: id, enable, admin, super_admin, online, sort_index, access_backoffice, access_crm, create_time, last_login_time, and every permission flag. All operators are supported.

Sorting: orderBy on any of the fields above. An unknown field falls back to sorting by id instead of failing.

Not supported

Aggregate columns - leads_count, clients_count, customers_count, deposits_amount, withdrawals_amount, net_deposit, deposits_count, and withdrawals_count - cannot be used in where or orderBy. They are not fields of ManagerRecord and are computed after the page has been selected. A condition on them is silently ignored; an orderBy on them falls back to id.


Behavior Notes

Pagination needs an explicit orderBy

Without orderBy, row order follows hash-map iteration of the staff cache and is not stable between requests. Paging without a sort can duplicate or skip rows. Always send orderBy together with limit / offset; ["id", "ASC"] is the cheapest stable choice. This matches the existing behavior of MngGetManagersByFilter.

Counters and amounts can disagree

Operation counters do not depend on an exchange rate and are always complete. When a rate for some account currency is unavailable, that currency's amount is omitted while its count is still included, so a row can show a non-zero deposits_count with an understated deposits_amount. Every occurrence is written to the server log as [STAFF] Currency conversion unavailable with the currency and manager id.

Unattributed data

Customers with assigned_manager_id = 0 and accounts with customer_id = 0 are attributed to no manager. The sum of a column across all rows is therefore lower than the platform-wide total. This is expected, not data loss.

Scope of the statistics

All-time. The command accepts no from / to arguments. Only assigned_manager_id is used for attribution; manager_id is deliberately ignored, with no fallback.


Errors

Status Error Description
400 INVALID_DATA Validation failed. The message field carries the validator text and names the offending parameter
401 PERMISSION_DENIED_ACCESS Router-level rejection: the session type is not SESSION_ADMIN
403 — The caller's manager access context could not be resolved: manager not found, disabled, or not a staff session

A 200 response with an empty rows array and count: 0 is a valid success response, not an error.