Skip to content

MLM Admin Applet

Run a member-gets-member referral programme: a program with a start and end date, members enrolled as participants, and the lineage of who introduced whom, up to ten levels deep, filled by invitation or by a participant’s QR link. The applet records the tree and pays nobody — the money is an MLM-type scheme in Commission Scheme, and the commission run uses the cycle’s scheme, not the one named on the program. Take the scheme design to the company’s legal adviser before the first invitation is sent; nothing here distinguishes paying on sales from paying on recruitment.

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.

Registry code 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. This page carries no captures — the repository ships none and static/images/ holds none.

Where it fits

DirectionDocument / appletHow it connects
UpstreamMembership AdminEvery participant, inviter and downline is a membership card; the member’s own MLM tab shows the uplines and downlines this applet holds
UpstreamCustomer Maintenance, EntityParticipants carry the member’s customer entity; entity contacts are named people under an entity who are not yet members
UpstreamUnified Contact CenterA program’s communication endpoint links name the e-mail / SMS / WhatsApp endpoints and message templates the invitation processor sends through
UpstreamCommission SchemeA program may name a scheme; the commission run is driven by MLM-type schemes, not by that field (see Lifecycle)
UpstreamCP Commerce AdminThe 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
DownstreamSales CommissionThe posting run reads the lineage 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 itemRouteWhat it shows
Entity ContactscontactsPeople under a customer entity who can be invited before they are members: Name, Parent Entity, Email, Phone, Status; create and edit
InvitationsinvitationAll invitations across programs: Inviter, Invitee, Program, Invite Method, Invite Status, Date Created, Expiry Date; + opens Invitation Create
ProgramsprogramPrograms: Program Code, Program Name, Commission Scheme, Website, Start Date, End Date; + opens Program Create
PayoutgenericNo 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:

TabContent
DetailsThe program header (Commission Scheme is shown at create only; the select is commented out on edit)
Communication EndpointsListing 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)
ParticipantsParticipant, Object Type, Referral Source, Date Created, Date Effective; create picks a membership card on a tab the screen spells Partcipant (the misspelling is the product’s); edit has QR Code and Details tabs and Delete
InvitationsThe same invitation screens as the top-level menu, filtered to the program; edit adds the Invitee field and Delete
LineageUpline, 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 the participant’s personal invitation link as a QR image, a copyable link and a PNG download — the self-service invitation link described under Lifecycle.

Settings and Personalization menus

EntryRoutePurpose
Field Settingssettings/field-settingsEight 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 Selectionsettings/default-selectionDefault Branch and Default Location (see Configuration — the save path is broken)
Webhook, Feature Visibility, permission listingssettings/…Shared platform screens; the settings menu’s default lands on Feature Visibility
Personalization → Default Selectionpersonalization/personal-default-selectionPer-user branch and location, same broken save path

Configuration

Before you can use it

PrerequisiteWhereWhy
Members with a card numberMembership AdminA participant is a membership card; the participant picker lists membership cards and nothing else (obj_type is always MEMBERSHIP in the UI)
Customer entitiesCustomer MaintenanceA 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 WhatsAppUnified Contact CenterThe invitation processor takes the channel from the program’s one ACTIVE endpoint link, and the sending endpoint and message template from its own scheduled-job settings (see Lifecycle); with no link, invitations stay CREATED
A storefront website with post-registration configured, and a landing URL containing <URL-KEY>CP Commerce AdminThe 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 cycleCommission Scheme, Sales CommissionWithout them the lineage earns nothing
The two queue processors scheduled: MLM_INVITATION_LINK_PROCESSOR and INVITATION_INSERTION_INTO_LINEAGE_PROCESSORSchedulerInvitations 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 listingsEach 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.

SettingWhereDeclaredRenderedPersistedConsumedEffect
DEFAULT_BRANCHDefault Selectionbuilt inline into the applet container’s extension rowsYes, branch drop-downBroken — changing the value raises an error and nothing is savedNo consumer outside the settings screensNone
DEFAULT_LOCATIONDefault Selectionas aboveYesBroken, as aboveNoneNone
Personal DEFAULT_BRANCH / DEFAULT_LOCATIONPersonalization → Default Selectionas aboveYesBroken — same commented subscriptionNoneNone
Eight Field Settings togglesField SettingsNo modelYesNo — Save has no handler; the component class is emptyNoNone (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 (OWNER, ADMIN, MEMBER, CREATE, UPDATE, DELETE, READ for each of programs, participants, lineages, invitations, entity contacts and endpoint links) plus TNT_API_DM_MLM_REGISTRATION_{OWNER, ADMIN}. Accepting an invitation and following a QR link need no sign-in, by design: they are used by people who are not yet users.

No menu is restricted, and the applet has no client-side permissions of its own.

Fields

Program

FieldMeaningRequiredNotes / validation
Program Name, Program CodeDisplay name and business keyYes (UI, ≤255 each)Backend does not check uniqueness of either — MLMErrors declares MLM_PROGRAM_HDR_OBJECT_CODE_ALREADY_EXISTS but the validator never raises it; DDL has no unique constraint. The commission report prints the program’s name, so two programs sharing a name cannot be told apart there — check the Programs listing before you reuse one
Start Date, End DateThe campaign windowYesBackend rejects null date_start / date_end; DDL date_end NOT NULL. Nothing else reads the window — an expired program still accepts participants and invitations
Commission SchemeA scheme from Commission SchemeNoMust exist if given; set at create only; not read by the commission run (see Lifecycle)
WebsiteA storefrontNoChecked to exist, then read by nothing: the QR link goes to URL Forward, and a storefront sign-up finds the program through the inviter’s key, whichever website the visitor registered on. The picker lists the first 100 websites only
URL ForwardThe public landing URL the QR link redirects toNoMust contain the literal <URL-KEY>; otherwise the redirect endpoint answers 404
StatusACTIVE / INACTIVEYesDefault ACTIVE

Participant — the form also carries Program (read-only) and Object Type, which offers MEMBERSHIP only.

FieldMeaningRequiredNotes / validation
ParticipantThe membership card, picked on the Partcipant tab (spelled that way on screen)YesUnique per program + entity + member — MLM_PROGRAM_PARTICIPANT_HDR_OBJECT_VIOLATE_UNIQUE_CONSTRAINTS_ON_PROGRAM_HDR_MEMBER_GUID_ENTITY_HDR…; program, entity and member guids must exist
Date EffectiveFrom when the participant countsNo — left empty, the server fills in the moment of savingDelays nothing. Only the listing’s date filter reads it; the commission run counts a participant from the moment the row is ACTIVE, whatever date this says
Referral SourceThe messaging channel that brought the participantNoStored as the channel’s id and shown as its name. A storefront sign-up writes the word INVITE instead, which matches no channel, so those participants show no referral source. The lineage job copies this value onto the downline’s lineage row
StatusACTIVE / INACTIVEYesGoverns the buying side of commission. The commission run finds a buyer’s programs through ACTIVE participant rows only, so an INACTIVE participant’s purchases pay no upline. It does not stop that person being paid: the run climbs the tree through lineage rows and never reads an upline’s participant status — see Lineage Status
Invite URL keyRandom key generated on create—Not shown as a field; unique in DDL; the storefront looks the inviter up by guid or this key

Lineage — the form also carries Lineage Type, a label nothing that pays commission reads: the form sends Membership, and the lineage job copies the invitation’s own type (MEMBERSHIP, ENTITY or ENTITY_CONTACT).

FieldMeaningRequiredNotes / validation
UplineThe parent participantYesParent and child participant, entity and member guids must all exist; parent ≠ child
DownlineThe child participantYesUnique per program + child — a downline has exactly one upline in a program
Referral SourceChannel that made the linkNoLeft empty on the form, the link saves with an empty referral source — the server rejects only a missing value, which the form never sends. …REFERRAL_SOURCE_IS_NULL_OR_EMPTY comes from an API call that omits the field
StatusACTIVE / INACTIVEYesThis is the switch the commission run obeys. It climbs from the buyer through ACTIVE lineage rows only, so setting a row INACTIVE stops the climb there: nobody above that link is paid on sales below it, and the downline’s own sales stop paying their upline. To stop paying one person while keeping the tree, a person has to decide which link to cut — the run offers no per-person switch
LevelDepth under the root—Computed, not entered: parent’s level + 1, or 1 when the parent has no lineage row; must stay within 0…10, also checked on delete

Invitation

FieldMeaningRequiredNotes / validation
Lineage TypeWhere the invitee comes from: MEMBERSHIP, ENTITY, ENTITY_CONTACTYesReveals a tab — Invite from Members / Invite from Entity / Invite from Entity Contacts — a multi-select; one invitation row is created per selected invitee
Program, InviterWhich program, and the participant doing the invitingYesThe inviter picker lists the chosen program’s participants. The inviter decides what the message carries: its link is the program’s URL Forward resolved with the inviter’s key, and an e-mail invitation is copied to the inviter’s entity e-mail
Expiry DateWhen the invitation lapsesYes (UI)Not enforced — nothing sets EXPIRED, and the send processor picks every CREATED invitation regardless of the date
Invite TypePARENT (sent by an inviter) or SELF_CLICKED (came in through a QR link)No—
Invite StatusCREATED, SENT, ACCEPTED, REJECTED, EXPIRED, LINEAGE_UPDATED (MLMConstants.InvitationStatus)NoPick Created when you create one. The form has no default and the server sets none, and the sending job picks only CREATED — an invitation saved with this blank is never sent and nothing says so. After that the processors drive it; the form lets an administrator set any value by hand
StatusACTIVE / INACTIVEYes (no default on the form)Both jobs pick ACTIVE invitations only, so INACTIVE is how you stop one from being sent or turned into a lineage row
Invitee (edit only)The child member / entity / contact—Existence checks only if present; no e-mail or phone format check

Entity Contact

FieldMeaningRequiredNotes / validation
NameThe personYes (≤255)Backend rejects empty
Parent EntityThe customer entity they belong toNo (UI)Must exist if given. A grouping for your own listing: an invitation made from a contact copies the contact’s e-mail and phone at the moment you create it and carries no parent entity, and later edits to the contact do not reach invitations already made
Email, Phone, StatusHow to reach them, and whether the contact is currentE-mail or phone: at least one; Status: yesFormat validated; duplicate e-mail / phone error codes exist in MLMErrors but are not checked. INACTIVE does not keep a contact out of an invitation — the invitee picker lists every contact, with the status shown beside it

Communication Endpoint Link

FieldMeaningRequiredNotes / validation
NameLabelYesNot sent — the create screen cannot save at all (Troubleshooting). What the sending job reads from a link is its code, which must be the channel in lowercase — sms, email or whatsapp — and no screen sets it
EndpointA contact-centre endpoint (e-mail, SMS, WhatsApp)YesProgram and endpoint must exist; unique per program + endpoint. The sending job does not send from this endpoint: the sender and the message template come from the scheduled job’s own settings
PriorityMeant as send order, lowest firstOptional in the UI, required by the backendEmpty fails with …PRIORITY_IS_NULL_OR_EMPTY — but see Troubleshooting: the create screen cannot reach the backend at all. It orders nothing today: the send job fails as soon as it has two links to sort
StatusACTIVE / INACTIVEYesKeep exactly one link ACTIVE per program. The send job reads ACTIVE links only, and with two or more it fails before sending anything; there is no fallback from one link to the next

Lifecycle and effects

Nothing posts, nothing moves. No record in this applet is a document: there is no journal, no stock movement, nothing for VOID to reverse. Every record is simply ACTIVE or INACTIVE.

How a downline joins — two paths

    stateDiagram-v2
    [*] --> CREATED: invitation saved in the applet
    CREATED --> SENT: the invitation-sending job sends via the program's endpoint links
    SENT --> ACCEPTED: invitee registers through the invitation link
    [*] --> ACCEPTED: QR / storefront sign-up with the inviter's key (SELF_CLICKED)
    ACCEPTED --> LINEAGE_UPDATED: the lineage job writes the lineage row
    LINEAGE_UPDATED --> [*]
    note right of CREATED
        REJECTED and EXPIRED exist as statuses
        but nothing sets them automatically
    end note
  

Path 1 — an invitation sent by the applet. You create an invitation for one or more invitees, with Invite Status Created. The scheduled job MLM_INVITATION_LINK_PROCESSOR picks every ACTIVE invitation whose Invite Status is CREATED and reads the program’s ACTIVE endpoint link, whose code names the channel. The endpoint it sends from and the contact-centre template it uses are not taken from the link: both are set in the scheduled job’s own settings, one pair per channel. The template’s <URL-KEY> is replaced by the program’s URL Forward resolved for the inviter, the inviter’s entity e-mail is copied in, and the message goes by SMS (needs an invitee phone), e-mail (needs an invitee e-mail) or WhatsApp. On a 2xx reply the status becomes SENT. There is no fallback: a program with two or more ACTIVE links fails the run before anything is sent, and a send that fails — an e-mail link with an invitee who has only a phone, say — stops the run at that invitation, which stays CREATED and is tried again on the next run until someone fixes it or sets it INACTIVE. Whoever owns the programme checks the Invitations listing after each run: anything still at CREATED has not gone. The invitee opens the link and registers — first and last name, e-mail or phone, a password and a referral source — without needing to sign in. If no confirmed login exists for that e-mail or phone, a new login is created and a six-digit code is e-mailed or texted, valid for eight hours, which the invitee enters with their password to finish. With or without that step, the platform 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. No match raises NO INVITATION CONTAINER COULD BE FOUND.

Path 2 — a QR or shared link. Each participant’s QR tab carries a personal link that needs no sign-in. It redirects to the program’s URL Forward with <URL-KEY> replaced by the participant’s key, or fails with not found when the URL Forward lacks the placeholder or is not a valid URL. When the visitor registers on the storefront with that key, the registration looks the inviter up, creates the new user’s entity, membership card and participant with referral source INVITE, and writes an invitation that is already ACCEPTED, type SELF_CLICKED, lineage type MEMBERSHIP. A website set to enforce MLM for every registration turns a missing key into a hard error that refuses the registration.

Both paths end the same way. The scheduled job INVITATION_INSERTION_INTO_LINEAGE_PROCESSOR takes every ACTIVE invitation at ACCEPTED that carries a child member and entity and a parent participant, member and entity, requires the child to already be a participant (it fails otherwise), creates the lineage row — computing its level — and sets the invitation to LINEAGE_UPDATED.

Lineage maintenance. Changing a row’s upline re-levels its whole subtree; deleting a row re-levels the descendants. Membership Admin’s MLM tab shows the chain each way.

Where the money is. The sales-commission posting run reads the lineage only for a scheme whose Commission Type is MLM: it loads the buyer entity’s participants, walks the lineage upward level by level (level 0 is the buyer, then each parent), and writes one intermediate commission line per upline per sales line with the program, level, member and upline entity, 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 — the report uses the program only to print its name. The member’s referral code on the membership card is a separate mechanism that MLM does not use.

Every record in this applet can also be read and written through the API, and a signed-in user can enrol themselves in a program and list their own participations — see the developer documentation.

Check that the scheme you are building is lawful, because nothing here will

This applet will happily build a tree ten levels deep and pay every level of it, and it makes no distinction between a scheme that pays on sales and a scheme that pays on recruitment. That distinction is the one a regulator cares about, and in Malaysia a direct-selling or multi-level scheme is licensed rather than merely registered. The commission run reads the lineage and the scheme’s per-level treatments and asks nothing else.

So there is one question the software cannot answer and will never raise: what is the upline actually being paid for? If the treatment at each level is driven by a downline’s sales, the tree is a commission structure. If it is driven by the act of recruiting — a payment that exists whether or not the new participant ever buys anything — it is a different thing in law, and no configuration on this screen makes it safe.

Who and when: whoever owns the programme takes the scheme design to the company’s legal adviser before the first invitation is sent, not after the tree exists — because a lineage is easy to build and hard to unwind, and the participants in it are customers. The evidence they need is small: the Commission Scheme’s per-level treatments, what each treatment is calculated on, and the maximum depth. They know the review is finished when they can say, for each level, which sale the payment came from.

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 MLM commission 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

SymptomCauseFix
Nothing happens when you save a Communication Endpoint link; invitations never leave CREATEDCreating a link from this screen does nothing, and the edit screen’s Save and Delete act on entity contacts, not linksCreate the links through the API; do not use the edit screen’s Delete, which targets an entity contact
Payout opens a “page not found” screenThe menu entry points at a route (generic) that does not existNone in the applet; commission payouts are in Sales Commission
Changing Default Branch or Location throws an error and nothing is savedThe settings screen never loads the applet container it writes intoDEFAULT_BRANCH / DEFAULT_LOCATION are read by nothing in this applet — they appear only in its two settings components, and the backend reads neither. Leave the screen alone
Lineage save through the API fails with …REFERRAL_SOURCE_IS_NULL_OR_EMPTYThe call left out the referral source field; the server rejects a missing value (an empty one passes, which is why the form never meets this)Send a referral source
Invitations stay at CREATED and nobody receives anythingInvite Status was left blank (never picked), the program has no ACTIVE link or more than one, the link’s code is not sms / email / whatsapp, or one unsendable invitation is stopping each runSet Invite Status to Created; keep one ACTIVE link; fix or set INACTIVE the invitation the run stops at
Endpoint-link save fails with …PRIORITY_IS_NULL_OR_EMPTY (via API)Priority is NOT NULL in the table and checked by the validatorSend a priority
…VIOLATE_UNIQUE_CONSTRAINTS_ON_PROGRAM_HDR_MEMBER_GUID_ENTITY_HDR… on participant createThe member is already a participant of this programOpen the existing participant
MLM_LINEAGE_HDR_OBJECT_PARENT_AND_CHILD_PARTICIPANT_GUID_IS_THE_SAME / …VIOLATE_UNIQUE_CONSTRAINTSUpline equals downline, or the downline already has an upline in this programCorrect the pair; delete the old link first to move a downline
MLM_LINEAGE_HDR_OBJECT_MAX_OR_MIN_LEVEL_IS_BREACHEDThe new link would put someone deeper than level 10Attach the downline higher in the tree
An invitation past its Expiry Date is still sent, or still acceptedExpiry is stored but never enforced; EXPIRED and REJECTED are never set automaticallySet the invitation INACTIVE (or its status to EXPIRED by hand) to stop it
An accepted invitation never becomes a lineage rowThe child is not yet a participant, or the invitation lacks child member / entity — the lineage processor skips itEnrol 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 FOUNDThe e-mail or phone entered on the acceptance page differs from the one on the invitation, or the inviter / program does not matchAccept 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 for a missing inviter keyThe website enforces an inviter key on every registration and the visitor arrived without oneRegister through a participant’s link, or turn off enforcement on the website’s post-registration settings
E-mail invitations arrive formatted like the SMS templateThe send processor passes the phone message-type code to the e-mail branchMake the phone template usable by e-mail until the processor is corrected
Two programs with the same codeNo code or name uniqueness check server-sideKeep codes unique yourself
Uplines are paid under a scheme other than the one on the programThe commission run uses the cycle’s scheme; the program’s Commission Scheme field is informationalConfigure the intended MLM scheme in the commission cycle

Related documentation

Last updated on