MLM Admin Applet
Overview
The MLM Admin applet is where you run a member-gets-member referral programme. You create a program (a referral campaign with a start and end date), enrol members from Membership Admin as participants, and the applet keeps the lineage — who introduced whom, one upline-to-downline link at a time, up to ten levels deep. New downlines arrive by invitation (an e-mail, SMS or WhatsApp message sent through a contact-centre channel, accepted on a registration page) or by scanning a participant’s QR link, which drops them onto your storefront’s sign-up page with the inviter’s key attached.
The applet records the tree. It pays nobody: the money is an MLM-type scheme in Commission Scheme, and the Sales Commission posting run walks the lineage up from the buyer and writes one commission line per upline level.
mlm-admin-applet (TNT-USER, ACTIVE, created 2022-12-28; the registry’s documentation_url still points at a Confluence page). Master-data applet: no document, no journal, no stock. There are no screenshots on this page — the repository ships none and static/images/ holds none.Where it fits
| Direction | Document / applet | How it connects |
|---|---|---|
| Upstream | Membership Admin | Every participant, inviter and downline is a membership card (bl_crm_membership_hdr); the member’s own MLM tab shows uplines and downlines read from this applet’s /uplines and /downlines endpoints |
| Upstream | Customer Maintenance, Entity | Participants carry the member’s customer entity; entity contacts are named people under an entity who are not yet members |
| Upstream | Unified Contact Center | A program’s communication endpoint links name the e-mail / SMS / WhatsApp endpoints and message templates the invitation processor sends through |
| Upstream | Commission Scheme | A program may name a scheme; the commission run is driven by MLM-type schemes, not by that field (see Lifecycle) |
| Upstream | CP Commerce Admin | The program’s Website and URL Forward point at a storefront; a storefront sign-up carrying an inviter’s key creates the downline’s entity, membership, participant and lineage |
| Downstream | Sales Commission | The posting run reads bl_mlm_lineage_hdr for MLM schemes and writes one intermediate commission line per upline level; the commission report shows MLM Program and MLM Member columns |
Screens and menus
Route root: applets/wavelet/erp/mlm-admin-applet/. Opening the applet lands on Invitations.
| Menu item | Route | What it shows |
|---|---|---|
| Entity Contacts | contacts | People under a customer entity who can be invited before they are members: Name, Parent Entity, Email, Phone, Status; create and edit |
| Invitations | invitation | All invitations across programs: Inviter, Invitee, Program, Invite Method, Invite Status, Date Created, Expiry Date; + opens Invitation Create |
| Programs | program | Programs: Program Code, Program Name, Commission Scheme, Website, Start Date, End Date; + opens Program Create |
| Payout | generic | No such route exists — the entry opens the applet’s 404 page. A Commission Report entry above it is commented out |
Program Create has a single Details tab. Program Edit has five tabs and a Delete button:
| Tab | Content |
|---|---|
| Details | The program header (Commission Scheme is shown at create only; the select is commented out on edit) |
| Communication Endpoints | Listing of the contact-centre endpoints linked to the program: Virtual Contact, Endpoint Name, Endpoint ID, Priority, Channel Name. The listing reads correctly; create, update and delete on this tab are not wired (see Troubleshooting) |
| Participants | Participant, Object Type, Referral Source, Date Created, Date Effective; create picks a membership card on a Participant tab; edit has QR Code and Details tabs and Delete |
| Invitations | The same invitation screens as the top-level menu, filtered to the program; edit adds the Invitee field and Delete |
| Lineage | Upline, Downline, Level, Invite Method (the referral-source channel name), Invite Status, Date Created; create picks an upline and a downline from the program’s participants; edit allows Delete |
The participant’s QR Code tab renders core2/tnt/dm/mlm/programs/{tenantCode}/participant/invite-by-guid/{participantGuid} as a QR image, a copyable link and a PNG download — the self-service invitation link described under Lifecycle.
Settings and Personalization menus
| Entry | Route | Purpose |
|---|---|---|
| Field Settings | settings/field-settings | Eight slide-toggles copied from the document-applet template (Unit Discount, SST/VAT/GST, WHT, Blanket Order, Segment, G/L Dimension, Profit Center, Project); bound to nothing, Save has no handler |
| Default Selection | settings/default-selection | Default Branch and Default Location (see Configuration — the save path is broken) |
| Webhook, Feature Visibility, permission listings | settings/… | Shared platform screens; the settings menu’s default lands on Feature Visibility |
| Personalization → Default Selection | personalization/personal-default-selection | Per-user branch and location, same broken save path |
Configuration
Before you can use it
| Prerequisite | Where | Why |
|---|---|---|
| Members with a card number | Membership Admin | A participant is a membership card; the participant picker lists membership cards and nothing else (obj_type is always MEMBERSHIP in the UI) |
| Customer entities | Customer Maintenance | A participant is unique per program + entity + member; entity contacts hang under a parent entity |
| Contact-centre endpoints and message templates for e-mail, SMS or WhatsApp | Unified Contact Center | The invitation processor sends through the program’s endpoint links in priority order; with none, invitations stay CREATED |
A storefront website with post-registration configured, and a landing URL containing <URL-KEY> | CP Commerce Admin | The QR link redirects to the program’s URL Forward with the key substituted; the storefront sign-up links the new user to the inviter |
An MLM-type commission scheme and a commission cycle | Commission Scheme, Sales Commission | Without them the lineage earns nothing |
The two queue processors scheduled: MLM_INVITATION_LINK_PROCESSOR and INVITATION_INSERTION_INTO_LINEAGE_PROCESSOR | Scheduler | Invitations are sent and accepted invitations become lineage rows only when these run |
API permissions TNT_API_DM_MLM_{PROGRAM_HDR, PROGRAM_PARTICIPANT_HDR, ENTITY_CONTACT_HDR, LINEAGE_HDR, INVITATION_HDR, PROGRAM_CC_ENDPOINT_LINK}_{READ, CREATE, UPDATE, DELETE} | Settings → permission listings | Each listing, create and edit screen |
Applet settings
Settings live in an applet-local settings container (not the shared field-configuration component); there is no applet-settings model file in this project. Tenant administrators can open the screens; nothing they save is read.
| Setting | Where | Declared | Rendered | Persisted | Consumed | Effect |
|---|---|---|---|---|---|---|
DEFAULT_BRANCH | Default Selection | built inline into the applet container’s extension rows | Yes, branch drop-down | Broken — the subscription that loads the applet container is commented out (default-settings.component.ts L32–40), so the save at L43 dereferences an unset object and throws | No consumer outside the settings screens | None |
DEFAULT_LOCATION | Default Selection | as above | Yes | Broken, as above | None | None |
Personal DEFAULT_BRANCH / DEFAULT_LOCATION | Personalization → Default Selection | as above | Yes | Broken — same commented subscription | None | None |
| Eight Field Settings toggles | Field Settings | No model | Yes | No — Save has no handler; the component class is empty | No | None (document-template residue) |
Model-only keys: none — the project has no settings model. Keys read at runtime without a declaration: none found.
Document behaviour settings
Not applicable — no documents.
Feature visibility / permissions
API permissions are the TNT_API_DM_MLM_* family in MlmPermissions.java (L14–95: OWNER, ADMIN, MEMBER, CREATE, UPDATE, DELETE, READ for each of the six tables) plus TNT_API_DM_MLM_REGISTRATION_{OWNER, ADMIN} (L99–102, marked in source as still to be revised). The acceptance and QR-redirect endpoints are anonymous by design (they are used by people who are not yet users).
Route guards: none — app.routing.ts declares no canActivate. The applet’s own PermissionResolver only maps target tables to services for the shared permission screens. No client-side permission definitions are registered for this applet (0 rows in bl_applet_client_side_perm_dfn for mlm-admin-applet, queried 2026-09-15).
Fields
Program (bl_mlm_program_hdr)
| Field | Meaning | Required | Notes / validation |
|---|---|---|---|
| Program Name | Display name | Yes (UI, ≤255) | Backend does not check uniqueness of code or name — MLMErrors declares MLM_PROGRAM_HDR_OBJECT_CODE_ALREADY_EXISTS but the validator never raises it; DDL has no unique constraint |
| Program Code | Business key | Yes (UI, ≤255) | As above |
| Start Date, End Date | The campaign window | Yes | Backend rejects null date_start / date_end (MlmProgramHdrDataConsistencyObject L77–87); DDL date_end NOT NULL. Nothing else reads the window — an expired program still accepts participants and invitations |
| Commission Scheme | A scheme from Commission Scheme | No | Must exist if given (L49–74); set at create only; not read by the commission run (see Lifecycle) |
| Website | A storefront (bl_cms_website_hdr) | No | Must exist if given |
| URL Forward | The public landing URL the QR link redirects to | No | Must contain the literal <URL-KEY>; otherwise the redirect endpoint answers 404 |
| Status | ACTIVE / INACTIVE | Yes | Default ACTIVE |
Participant (bl_mlm_program_participant_hdr)
| Field | Meaning | Required | Notes / validation |
|---|---|---|---|
| Program | The program being edited | Yes (read-only) | — |
| Object Type | What kind of record participates | Yes | UI offers MEMBERSHIP only |
| Participant | The membership card, picked on the Participant tab | Yes | Unique per program + entity + member — MLM_PROGRAM_PARTICIPANT_HDR_OBJECT_VIOLATE_UNIQUE_CONSTRAINTS_ON_PROGRAM_HDR_MEMBER_GUID_ENTITY_HDR… (L56–69); program, entity and member guids must exist |
| Date Effective | From when the participant counts | Yes (backend, L118–130) | — |
| Referral Source | The channel (bl_msg_channel_hdr) that brought the participant | No | — |
| Status | ACTIVE / INACTIVE | Yes | — |
| Invite URL key | Random key generated on create (MlmProgramParticipantHdrService L34–37) | — | Not shown as a field; unique in DDL; the storefront looks the inviter up by guid or this key |
Lineage (bl_mlm_lineage_hdr)
| Field | Meaning | Required | Notes / validation |
|---|---|---|---|
| Upline | The parent participant | Yes | Parent and child participant, entity and member guids must all exist (MlmLineageHdrDataConsistencyObject L51–127); parent ≠ child (L211–218) |
| Downline | The child participant | Yes | Unique per program + child (L180–194) — a downline has exactly one upline in a program |
| Referral Source | Channel that made the link | Optional in the UI, required by the backend (L130–136) | Leaving it empty fails the save with …REFERRAL_SOURCE_IS_NULL_OR_EMPTY |
| Lineage Type | Kind of link | Yes | UI offers membership only |
| Status | ACTIVE / INACTIVE | Yes | — |
| Level | Depth under the root | — | Computed, not entered: parent’s level + 1, or 1 when the parent has no lineage row (MlmLineageHdrService.getLevel L114–130); must stay within 0…10 (MAX_LEVEL = 10, L29–30, L221–227), also checked on delete (L460–468) |
Invitation (bl_mlm_invitation_hdr)
| Field | Meaning | Required | Notes / validation |
|---|---|---|---|
| Program | Which program the invitee joins | Yes | Must exist |
| Lineage Type | Where the invitee comes from: MEMBERSHIP, ENTITY, ENTITY_CONTACT | Yes | Reveals a tab — Invite from Members / Invite from Entity / Invite from Entity Contacts — a multi-select; one invitation row is created per selected invitee (mlm-invitation-create.component.ts L193–273) |
| Inviter | The parent participant | Yes | Must exist |
| Expiry Date | When the invitation lapses | Yes (UI) | Not enforced — nothing sets EXPIRED, and the send processor picks every CREATED invitation regardless of the date |
| Invite Type | PARENT (sent by an inviter) or SELF_CLICKED (came in through a QR link) | No | — |
| Invite Status | CREATED, SENT, ACCEPTED, REJECTED, EXPIRED, LINEAGE_UPDATED (MLMConstants.InvitationStatus) | No | Normally driven by the processors; the form lets an administrator set any value by hand |
| Status | ACTIVE / INACTIVE | Yes | — |
| Invitee (edit only) | The child member / entity / contact | — | Existence checks only if present (MlmInvitationHdrDataConsistencyObject L32–139); no e-mail or phone format check |
Entity Contact (bl_mlm_entity_contact_hdr)
| Field | Meaning | Required | Notes / validation |
|---|---|---|---|
| Name | The person | Yes (≤255) | Backend rejects empty |
| Parent Entity | The customer entity they belong to | No (UI) | Must exist if given (MlmEntityContactHdrDataConsistencyObject L49–58) |
| Email, Phone | How to reach them | At least one (L59–65, …DOES_NOT_CONTAIN_ANY_CONTACT_INFORMATION) | Format validated (L66–77); duplicate e-mail / phone error codes exist in MLMErrors but are not checked |
| Status | ACTIVE / INACTIVE | Yes | — |
Communication Endpoint Link (bl_mlm_program_cc_endpoint_link)
| Field | Meaning | Required | Notes / validation |
|---|---|---|---|
| Name | Label | Yes | — |
| Endpoint | A contact-centre endpoint (e-mail, SMS, WhatsApp) | Yes | Program and endpoint must exist; unique per program + endpoint (L87–95) |
| Priority | Send order, lowest first | Optional in the UI, required by the backend (L78–84; DDL NOT NULL) | Empty fails with …PRIORITY_IS_NULL_OR_EMPTY — but see Troubleshooting: the create screen cannot reach the backend at all |
| Status | ACTIVE / INACTIVE | Yes | The send processor uses ACTIVE links only |
Lifecycle and effects
Posting proof: nothing posts, nothing moves. ServerDocTypes.java, JournalPostingTypeHandler.java and GenericDocumentTypeHandler.java contain no entry matching MLM, LINEAGE or REFERRAL (grep at backend commit 45edc03526: zero hits in each). No record in this applet is a generic document; there is no amount or quantity signum, no Dr/Cr equation, no stock processor, nothing for VOID to reverse. Every table carries only status ACTIVE / INACTIVE.
How a downline joins — two paths
stateDiagram-v2
[*] --> CREATED : invitation saved in the applet
CREATED --> SENT : MLM_INVITATION_LINK_PROCESSOR sends via the program's endpoint links
SENT --> ACCEPTED : invitee registers through mlm/invitation-acceptance
[*] --> ACCEPTED : QR / storefront sign-up with the inviter's key (SELF_CLICKED)
ACCEPTED --> LINEAGE_UPDATED : INVITATION_INSERTION_INTO_LINEAGE_PROCESSOR writes the lineage row
LINEAGE_UPDATED --> [*]
note right of CREATED
REJECTED and EXPIRED exist in the enum
but nothing sets them automatically
end note
Path 1 — an invitation sent by the applet. You create an invitation for one or more invitees. MLM_INVITATION_LINK_PROCESSOR (MlmInvitationLinkProcessor.java L45; registered in JobProcessorClassName.java L272) picks every ACTIVE invitation whose status is CREATED (L154–163), loads the program’s ACTIVE endpoint links in priority order (L142–151), builds the message from a contact-centre template — the template’s <URL-KEY> is replaced by the program’s URL Forward resolved for the inviter, and the inviter’s entity e-mail is copied in (EmailEndpointService.java L53–90) — and sends by SMS (needs an invitee phone), e-mail (needs an invitee e-mail) or WhatsApp (L118–132). On a 2xx reply the status becomes SENT (L176–179); on failure it tries the next link, up to three attempts, then throws (L102–114). The invitee opens the link and posts to POST core2/tnt/dm/mlm/invitation-acceptance (anonymous; MlmRegistrationController.java L29) with the inviter participant guid, first and last name, e-mail or phone, a password and a referral source (MlmRegistrationRequest.java L7–13). If no confirmed login exists for that e-mail or phone, the backend creates a login subject, a registration record that expires in eight hours, and e-mails or texts a six-digit code and registration key (MlmRegistrationService.java L84–114, L270–313, L392–441); the caller then finishes with POST …/invitation-acceptance/password-creation (code, key, password; L477–560). With or without that step, the backend finds or creates the customer entity, finds or creates the membership card, creates the participant, and sets the matching invitation — same inviter and program, same e-mail or phone — to ACCEPTED with the child member and entity filled in (L349–367). No match raises NO INVITATION CONTAINER COULD BE FOUND.
Path 2 — a QR or shared link. Each participant’s QR tab exposes GET core2/tnt/dm/mlm/programs/{tenantCode}/participant/invite-by-guid/{participantGuid} (anonymous; MlmProgramHdrController.java L197–230). It answers with a 302 to the program’s URL Forward with <URL-KEY> replaced by the participant guid, or 404 when the URL lacks the placeholder or is not a valid URL. When the visitor registers on the storefront with that key as invited_by, post-registration (PostRegistrationService.java L95–96) calls TenantUserConfigurationService.stepLinkUserToMLM (L995–1021): it looks the inviter up by guid or invite key (MlmProgramParticipantHdrUow.getByInviteKeyOrGuid L332–341), creates the new user’s entity, membership card and participant with referral source INVITE (L1043–1072), and writes an invitation that is already ACCEPTED, type SELF_CLICKED, lineage type MEMBERSHIP (L1075–1103). A website whose post-registration request sets mlm_enforce_for_all turns a missing key into the hard error INVITE_URL_KEY_NOT_FOUND_BUT_THE_MLM_ENFORCE_FOR_ALL_SET_TO_TRUE (L1016–1018).
Both paths end the same way. INVITATION_INSERTION_INTO_LINEAGE_PROCESSOR (InvitationInsertionIntoLineageProcessor.java L39; registered L265) takes every ACTIVE invitation at ACCEPTED that carries a child member and entity and a parent participant, member and entity (L78–95), requires the child to already be a participant (NO_PARTICIPANT_FOUND otherwise, L141–156), creates the lineage row through MlmLineageHdrService.create — which computes the level — and sets the invitation to LINEAGE_UPDATED (L106–111).
Lineage maintenance. Changing a row’s upline re-levels its whole subtree (MlmLineageHdrService L42–50, MlmLineageHdrUow.updateForChildrenLevels L530–580); deleting a row re-levels the descendants (L90–112). GET …/mlm/lineages/uplines/{participantGuid} and /downlines/{participantGuid} (MlmLineageHdrController.java L249, L271) return the chain each way; Membership Admin’s MLM tab reads them.
Where the money is. The sales-commission posting run (CommissionPostingServices.java) reads the lineage only for a scheme whose Commission Type is MLM (L205): it loads the buyer entity’s participants (L209–210), walks the lineage upward level by level (getMlmUplineMembers L731–752 — level 0 is the buyer, then each parent), and writes one bl_fi_sale_commission_intermediate_processing_txn_line per upline per sales line with the program guid, level, member and upline entity (L273–287), skipping levels whose treatment is zero. The scheme applied is the one the commission cycle selects; the program’s own Commission Scheme field is not consulted — CommissionReportUow.java L460–463 joins the program only to print its name on the report. The member’s referral_code on the membership card is a separate mechanism that no MLM code reads.
Endpoints (all under core2/tnt/dm/mlm/): programs, program-participants (plus POST …/login-ep for a logged-in user to enrol themselves and GET …/login-entity-ep/query for their own participations), lineages (plus /uplines/…, /downlines/…), invitation, entity-contacts, mlm-program-cc-ep-link — each with POST, PUT, DELETE /{guid}, GET, GET /{guid}, GET /query — and the two anonymous acceptance endpoints above.
Related applets
- Membership Admin — participants are its membership cards; its member edit screen’s MLM and Referrals tabs show the tree this applet builds.
- Commission Scheme — the
MLMcommission type and its per-level treatments (up to ten uplines) are defined there. - Sales Commission — the posting run that walks the lineage and the report that shows MLM Program and MLM Member per line.
- Customer Maintenance and Entity — the entities behind participants and entity contacts; acceptance creates a customer entity when none exists.
- Unified Contact Center — the e-mail, SMS and WhatsApp endpoints and message templates the invitation processor sends through.
- CP Commerce Admin — the storefront whose sign-up completes the QR path and whose post-registration settings can make the inviter key mandatory.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Nothing happens when you save a Communication Endpoint link; invitations never leave CREATED | The endpoint-link effects file is entirely commented out, so the create action has no handler; the edit screen is a copy of Entity Contact edit and its Save and Delete dispatch entity-contact actions | Insert the link rows through POST core2/tnt/dm/mlm/mlm-program-cc-ep-link; do not use the edit screen’s Delete, which targets an entity contact |
| Payout opens a “page not found” screen | The menu entry points at a route (generic) that does not exist | None in the applet; commission payouts are in Sales Commission |
| Changing Default Branch or Location throws an error and nothing is saved | The settings screen never loads the applet container it writes into | Nothing reads these values; leave the screen alone |
Lineage save fails with …REFERRAL_SOURCE_IS_NULL_OR_EMPTY | Referral Source is optional on the form but required by the validator | Pick a referral source |
Endpoint-link save fails with …PRIORITY_IS_NULL_OR_EMPTY (via API) | Priority is NOT NULL in the table and checked by the validator | Send a priority |
…VIOLATE_UNIQUE_CONSTRAINTS_ON_PROGRAM_HDR_MEMBER_GUID_ENTITY_HDR… on participant create | The member is already a participant of this program | Open the existing participant |
MLM_LINEAGE_HDR_OBJECT_PARENT_AND_CHILD_PARTICIPANT_GUID_IS_THE_SAME / …VIOLATE_UNIQUE_CONSTRAINTS | Upline equals downline, or the downline already has an upline in this program | Correct the pair; delete the old link first to move a downline |
MLM_LINEAGE_HDR_OBJECT_MAX_OR_MIN_LEVEL_IS_BREACHED | The new link would put someone deeper than level 10 | Attach the downline higher in the tree |
| An invitation past its Expiry Date is still sent, or still accepted | Expiry is stored but never enforced; EXPIRED and REJECTED are never set automatically | Set the invitation INACTIVE (or its status to EXPIRED by hand) to stop it |
| An accepted invitation never becomes a lineage row | The child is not yet a participant, or the invitation lacks child member / entity — the lineage processor skips it | Enrol the child as a participant; if the status was set to ACCEPTED by hand, fill in the invitee |
Acceptance fails with NO INVITATION CONTAINER COULD BE FOUND | The e-mail or phone entered on the acceptance page differs from the one on the invitation, or the inviter / program does not match | Accept with the invited address, or create a matching invitation |
| The QR link answers 404 or “Invalid URL” | The program’s URL Forward is empty, is not a valid URL, or lacks <URL-KEY> | Set URL Forward to the storefront landing page with <URL-KEY> in it |
Storefront sign-up rejected with INVITE_URL_KEY_NOT_FOUND_BUT_THE_MLM_ENFORCE_FOR_ALL_SET_TO_TRUE | The website enforces an inviter key on every registration and the visitor arrived without one | Register through a participant’s link, or turn off enforcement on the website’s post-registration settings |
| E-mail invitations arrive formatted like the SMS template | The send processor passes the phone message-type code to the e-mail branch (MlmInvitationLinkProcessor.java L122–125) | Make the phone template usable by e-mail until the processor is corrected |
| Two programs with the same code | No code or name uniqueness check server-side | Keep codes unique yourself |
| Uplines are paid under a scheme other than the one on the program | The commission run uses the cycle’s scheme; the program’s Commission Scheme field is informational | Configure the intended MLM scheme in the commission cycle |
Related documentation
- Membership module
- Membership applets
- Commission Scheme — the
MLMcommission type - Integrate an external system — headers for the
core2/tnt/dm/mlm/endpoints