Manage (back-office configuration)
What it is
Everything that configures the rest of the application. ~79 components, all under
/manage, with the parent route denying the two restricted groups
(REST_UW_ACCESS, ADVANCE_AUDITORS) and per-screen permissions enforced inside.
The default child route is users/list.
Screen map
| Route | Folder | Purpose |
|---|---|---|
|
|
Bulk CSV imports and CLI actions. See bulk actions. |
|
|
Internal user administration. |
|
|
Permission groups. |
|
|
Key/value configuration store. Has its own state slice. |
|
|
Typed configuration records (task templates, email templates, …). |
|
|
Event rules v1. See events. |
|
|
Event rules v2 (beta). |
|
|
The raw domain-event stream with badges, filters and preview. |
|
|
Onboarding templates. See onboarding. |
|
|
Email templates and configuration. See emails. |
|
|
Fee schedules and tiers. See fees and reserves. |
|
|
Contract templates. |
|
|
The accounting close screen. |
— |
|
Shared "access denied" dialog used across the app. |
Bulk actions and CLI
/manage/batch-imports is the bulk-operations console. BatchImportService is the
only service in the codebase that spans four backends — internalApiUrl,
cliApiUrl, actionsToolsUrl and b2bDocumentsApiUrl.
| Import | Endpoint | Notes |
|---|---|---|
Manual bank accounts |
|
|
Pre-funded cards |
|
|
Bulk notes |
|
|
EFS upload |
|
Card programme data. |
Supplier credit |
|
|
Weekly purchases |
|
|
UCC upload |
|
Bulk UCC filing data. See underwriting. |
CLI actions |
|
Arbitrary CLI operations exposed as forms. |
Client-side CSV parsing
getNotes(data, user) reads the file with FileReader and builds note records in the
browser:
const lines = e.target.result.split('\n');
for (let i = 1; i < lines.length; i++) { // skips the header row
const line = lines[i].split(','); // naive comma split
if (line[0]) {
notes.push({ target_type: 'note', target_uuid: line[0],
body: line[1], is_pinned: line[2] === 'true', user });
}
}
|
This is a naive split on |
Job monitoring
Long-running imports are executed by the B2B task manager, and progress is polled or streamed:
getLastExecution(name) → B2bWorkflowService.getLastExecution(name)
getListOfActivities(name) → getLastExecution(name) then getListOfActivities(task.id)
The underlying stream is server-sent events. See real-time streams.
Users
UsersService on internalApiUrl (logins and perms):
| Endpoint | Purpose |
|---|---|
|
Users filtered by permission code. |
|
One user → |
|
Update. |
|
Delete. |
|
A user’s permissions. |
|
Sub-permissions of a permission (cached in an instance field after first call). |
getUser(username, jwtToken) is the call AuthEffects makes at login to load the user
and their perms — see authentication.
checkPermission(permCode) is what SubPermissionsGuard uses.
The name-field hack
saveLogin contains an explicit, commented workaround:
const payload = {
...Serialize(data, UserModel),
firstName: data.firstname, lastName: data.lastname,
firstname: undefined, lastname: undefined,
};
// sorry for this hack but I don't want to broke other places where we use this model
UserModel uses firstname/lastname; the update endpoint expects
firstName/lastName. Rather than change the model (used widely), the service rewrites
the payload. Know this before "fixing" the model casing.
Caching
UsersService keeps three BehaviorSubject caches — usersSubject, groupsSubject,
usersAndGroupsSubject — exposed as users, groups and
getPreparedCombinedList(). The combined list is fetched once with
combineLatest([getUsers(), getGroups()]) and cached; there is no invalidation, so a
user created in this session will not appear in an assignee dropdown until reload. See
state management.
Permission groups
/manage/perm-groups with perm-groups-list/, -add/, -edit/.
PermissionModel / PermissionBaseModel (shared/users/permission.model.ts) and
UserPermModel (user-perm.model.ts) are the models; the vocabulary is
USER_PERMISSION (shared/users/user-permissions.enum.ts).
Groups nest: a permission has sub-permissions
(perms/:permCode/sub-perms), which is what requireOneOfSubPermissions route data
checks. Requires INT_PERMS_ADM. See
authorisation.
Global settings
The most load-bearing screen in Manage: it is where feature flags are stored.
GlobalSetting (manage/global-settings/models/global-setting.model.ts):
interface GlobalSetting {
config_key: string; config_type: string; config_value: any;
created_at: number; updated_at: number;
instance_id: number;
owner_email: string; owner_uuid: string;
updated_by: string; updated_by_name: string;
uuid: string;
}
config_value is untyped any — its shape depends on config_key.
GlobalSettingsService on actionsToolsUrl with an x-instanceid header
(env.instanceId, 8001):
| Endpoint | Purpose |
|---|---|
|
All settings. |
|
One setting. |
|
All settings of a type. |
|
Create. |
|
Update. |
|
Delete. |
The service is a facade over the store slice: getAll() selects from
globalSettings, dispatches GlobalSettingsFetchAll on a miss or a forced refresh, and
every mutation dispatches GlobalSettingsUpdateConfig. Call the service; do not select
the slice directly from a component.
Screens: new-setting/, edit-setting/, delete-setting/, and templates/ for
template-valued settings.
Feature flags live here under the keys FEATURE_FLAGS_IA and FEATURE_FLAGS_EVA, with
config_value = { flags: 'A,B,C', lastChange: <timestamp> }. See
feature flags.
Configs
/manage/configs administers typed configuration records. ConfigsService
(shared/configs/, with an abstract base and a mock) reads them through
GlobalSettingsService — configs are global settings with a known config_type.
ConfigTypes:
| Member | config_type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ConfigEmailTypes narrows the list to the six email types, and
ConfigEmailTypesNames pairs each with a display name and a can_add_new flag —
some email config types are singletons.
|
Task statuses and priorities exist twice: as TypeScript enums in
|
The event stream
/manage/events shows raw domain events with event-badge/, event-filters/ and
event-preview/. It is backed by EventsService’s Elasticsearch query against the
`em_events index on eventRulesOldUrl. The sidebar reaches it through
navigateToEvents() rather than a plain routerLink.
Event rules — the reactive layer over the same stream — are documented in events.
Contracts and month-end close
| Screen | Detail |
|---|---|
|
Contract templates via |
|
A single component ( |
Permissions
| Permission | Governs |
|---|---|
|
Users and permission groups. |
|
Global settings. |
|
Email settings. |
|
Event rules. |
|
Report administration. |
|
Broad administrative access. |
|
CRM administration. |
|
DEV Admin only — see DEV Admin. |
The /manage parent route denies REST_UW_ACCESS and ADVANCE_AUDITORS; finer control
is per-screen and backend-enforced.
Gotchas
| Issue | Detail |
|---|---|
Naive CSV parsing |
Bulk notes split on |
Four backends in one service |
|
|
The documented hack in |
Uninvalidated user cache |
|
|
Shape depends on |
Global settings are the flag store |
Editing |
Duplicated task vocabularies |
Enums in code and configs in the database. |
|
It triggers a job; there is no payload to review before sending. |
Two event-rule generations |
Both routed and both live. |