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 = 1andsuper_admin = 1) receive every visible manager. - Other admins receive only managers of their own brand, excluding super admins.
When the caller's
brandis empty, the result is empty. - Recovery manager
id=1is 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:
cmdisOP_BALANCE_IN(6) orOP_BALANCE_OUT(8). Credit and bonus commands are excluded.stateisTS_CLOSED_NORMAL. Pending requests, declined requests, and deleted operations are excluded.reasonis a client reason. Internal platform movements are excluded:TR_REASON_PROP(8),TR_REASON_COPY_REWARD(10), andTR_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¶
- Exclude recovery manager
id=1. - Apply brand scope. Skipped for super admins.
- Apply the
access_crm/access_backofficeshortcut filters. - Apply the
wherefamily of conditions. - Apply
orderBy. - Set
countfrom the full matching set. - Slice the page with
limit/offset. - 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.