Skip to content

Document Numbering

You have just watched a cashier at branch GS-KV-01 ring up a sale, and the cash bill says Doc No (Tenant) 1000583. The next cash bill, at GS-PEN-01 four hundred kilometres away, says 1000584. Your auditor wants to know why the two branches share one sequence, whether numbers can be skipped or reused, and whether the branch can have a sequence of its own. This page answers those three questions from the code that assigns the numbers, tells you which numbers you can show on a listing and a printout, and is honest about the one thing most people ask for and cannot have from a screen: a prefix or a starting number of your choosing. Reading it takes about ten minutes; there is nothing to set up, because numbering runs on its own from the first document.

Meet GadgetSphere

GadgetSphere Sdn Bhd (GS) has 22 retail branches, GadgetSphere Online (GSO) has a fulfilment centre, and GadgetSphere Distribution (GSD) invoices corporate accounts. All three companies live in one BigLedger tenant. On a Saturday the group writes about 1,800 cash bills, 40 sales invoices, 60 receipt vouchers and a handful of stock transfers, from 22 tills and six back-office desks at once. Every one of those documents needs a number that is unique, never reused, and handed out fast enough that two tills pressing Save in the same second do not wait for each other.

One table numbers everything

BigLedger keeps a single counter table, app_sequence_counter, and every running number in the tenant — cash bills, invoices, vouchers, stock documents, customer codes, job orders — is drawn from it. The developers’ own description of the table:

“This table is used to calculate all sort of running numbers needed by various documents, journals, at different level (global counter, company counter, branch counter etc). This table will be shared by various module, it is the universal counter per instance of the application.” — table comment on app_sequence_counter

A counter row is identified by what it counts and for whom:

ColumnWhat it holdsExample for a GadgetSphere cash bill
doc_typeThe document type being countedINTERNAL_SALES_CASHBILL
module_nameA second qualifier, used by non-document countersempty for documents; CUSTOMER_ID for customer codes
composite_guid_01“usually, this one refers to the global GUID” — in practice the tenant codethe tenant code
composite_guid_02“usually, this one refer to the company GUID”the GS company’s guid
composite_guid_03“usually, this one refer to the branch GUID”the GS-KV-01 branch’s guid
sequence_num“the running number in digits” — the last number handed out (see the pool, below)1000999

There are eleven composite_guid slots, so a counter can be as narrow as the engine needs; documents use up to four. The same document type at the same level is always the same row, and a row is created the first time it is needed — nobody sets counters up.

Adoption. 80 of the 90 tenants hold counter rows; a median tenant has about 70 counters across about 10 document types — a dozen at tenant level, about 35 at company level and about 25 at branch, location or device level. The remaining ten tenants have never finalised a numbered document.

The three numbers on every document

A generic document carries several running numbers side by side, and they are assigned at different moments:

Column on bl_fi_generic_doc_hdrCounter keyShown asAssigned
server_doc_1tenant + document typeDoc No (Tenant)the moment the document is created, even as a draft
server_doc_2tenant + company + document typeDoc No (Company)when the document becomes FINAL
server_doc_3tenant + branch + document typeDoc No (Branch)when the document becomes FINAL
server_doc_locationtenant + company + location + document typenot shown on the standard listingswhen the document becomes FINAL
server_doc_4tenant + POS device + document typenot shown on the standard listingswhen the document becomes FINAL, only for documents that carry a device

So the cashier’s 1000583 is the tenant number: one sequence per document type across all three companies and all 22 branches, and it exists from the first Save. The branch number the auditor wants also exists — GS-KV-01’s own cash-bill sequence — but it is only stamped at FINAL and the listing hides it by default. Step 1 below turns it on.

Three things follow from the table that catch people out:

  • The number on a draft can be lower than the number on a bill finalised earlier. Tenant numbers are handed out at creation; a draft made at 09:00 and finalised at 17:00 keeps its 09:00 number.
  • A voided or discarded document keeps its number. Nothing gives a number back once it is written on a document. The one exception is a document whose creation is rolled back before the number is written — that number returns to the pool and is reused, which is the “no gaps” guarantee described next.
  • The numbers are plain integers. No prefix, no year, no zero-padding, no branch code — the listing shows the branch code in its own column beside the number, and a printable format can compose the two. The e-Invoice number (einvoice_number) is a separate thing assigned by LHDN’s system, not by this counter.
Custom documents (INTERNAL_CUSTOM) are the only type numbered per sub-type: their counter key also includes client_doc_type, so each custom document kind gets its own sequence. Every other document type has one sequence per level regardless of what is on the document.

Why two tills never wait for each other — the pool

Every counter that is drawn on has a pool of pre-issued numbers in app_sequence_counter_pool (67 of 90 tenants, about 5 million pooled numbers in total; a median tenant keeps about 20,000 in reserve). It works like this:

  1. When a document needs a number, the engine deletes the lowest available pool row for that counter and uses it. The delete is FOR UPDATE SKIP LOCKED, so two tills asking at the same instant each get their own row without queuing.
  2. If the pool for that counter is empty, the engine moves the counter on by 500 in a transaction of its own and writes those 500 numbers into the pool, then takes the lowest.
  3. After every draw it checks, in the background, whether fewer than 100 numbers remain and tops up by another 500 if so.

Because the pool row is deleted inside the same database transaction that writes the number onto the document, a document that fails to save gives its number straight back — the next document takes it. That is why BigLedger has no “reserved but never used” gaps, and also why the sequence_num on the counter row is always ahead of the highest number actually on a document: it is the top of the pool, not the last number used.

The first number is 1,000,000. A counter that has never existed is created at the platform default of 999,999 and the first number drawn is one more. The table comment says the default “should be … 1000”; the shipped default is 999,999, which is why every tenant’s documents start in the millions.

What you can do — and how

Step 1: Show the company and branch numbers on the listing

The document applet › Settings › Application Settings — and the role’s permissions

The outcome: GS-KV-01’s cash-bill listing shows three number columns — tenant, company and branch — so the branch manager can see their own sequence.

Each column is governed by a pair: a tenant-wide hide setting and a per-role show permission. On the Sales Invoice listing they are HIDE_SERVER_DOC_1 / SHOW_DOC_NO_TENANT, HIDE_SERVER_DOC_2 / SHOW_DOC_NO_COMPANY and HIDE_SERVER_DOC_3 / SHOW_DOC_NO_BRANCH; the cash bill and the other document applets use the same pattern. Leave the hide setting off and the column appears for everyone; turn it on and only roles holding the matching show permission see it — but only if that permission code is seeded for the applet, which for many applets it is not (see the applet’s own reference page under Feature visibility).

The five Client Doc columns (client_doc_1 to client_doc_5) are different: they are free text. The developers’ note on client_doc_1 is “server_doc_1 from client in case of peer to peer data sync” — a document imported from another system, an online marketplace or a spreadsheet keeps the number it had there in a Client Doc column, and its BigLedger number goes in the server columns. The listing’s quick search looks in server_doc_1, all five client columns, doc_reference and the e-Invoice number by default.

The commonest failure: the branch column is on, and it is blank on half the rows. Those are drafts and documents that were never finalised — the company and branch numbers are only stamped at FINAL.

Step 2: Put the right number on the printout

Organisation › Branch › Default Printable Format, or the printable format itself

Printable formats are the place a prefix lives, if you want one: a format prints server_doc_3 under a heading of your choosing, and it can print the branch code next to it, so GS-KV-01’s bill can read KV01 / 1000017 while the tenant number stays the audit trail. How a branch picks its layout is on Printable Formats and Per-Branch Defaults.

Step 3: Start a sequence at a number of your choosing — before the first document

There is no screen for this. Not in the Organisation applet, not in Tenant Admin, not anywhere in the applet registry as of 2026-09-16. What the code supports is a START row: when a counter is created for the first time, the engine looks for a row with the same key and txn_type = START and begins from its sequence_num instead of 999,999. The developers described it as “START - The first running number of the document”.

Two facts decide whether this can work for you:

  • It only applies to a counter that does not exist yet. If a single cash bill has ever been numbered at that level, the COUNTER row exists and any START row is ignored. To move an existing counter you change the COUNTER row’s sequence_num — and, because up to 500 numbers are already sitting in the pool, those are handed out first.
  • The only write path is the generic table API (core2/tnt/ms/pg/app_sequence_counter), which accepts the row as-is with no validation. This is an administrator’s or BigLedger support’s task, done at go-live, not something to try on a live tenant.

For GadgetSphere, wanting cash bills to begin at 5,000,001 on the day the tills go live, the row is: txn_type START, doc_type INTERNAL_SALES_CASHBILL, module_name empty, composite_guid_01 = the tenant code, sequence_num 5000000, status ACTIVE. The first cash bill then takes 5,000,001 and the pool fills with 5,000,001–5,000,500. A company- or branch-level start needs its own row with the company or branch guid in the next slot.

No tenant has ever done this. The aggregate run on 2026-09-16 found no START, SKIP or QUEUE row in any of the 90 tenants — every row is a COUNTER. SKIP (“numbers we want to skip”) and QUEUE (“numbers re-used and put back into the queue”) are described in the table comment and are not read by any code path at all; the pool does the reuse job QUEUE was designed for.

Step 4: Reset a sequence (a job, not a screen)

No screen of its own — a background job processor, queued through the platform’s job machinery (see the Scheduler applet, which pairs a processor with a cron expression and a JSON payload)

A job processor named App Sequence Counter Sequence Reset Processor (queue code APP_SEQUENCE_COUNTER_SEQUENCE_RESET_PROCESSOR, “meant to reset sequence no in the app sequence counter table”) takes two properties, doc_type and reset_value, and sets sequence_num to the reset value on every ACTIVE counter of that document type — tenant, every company, every branch, every location, every device, in one statement. It does not touch the pool, so the numbers already pooled (up to 500 per counter) keep being issued until each pool drains, and only then does the reset show. Use it for what it is: a way to restart a type’s numbering across the whole tenant, for example at a system migration, with the pool caveat in mind. It is not a per-branch reset.

Customer, supplier and employee codes come from the same table

When you save a customer without typing a code, the backend builds one from a per-type prefix and a running number: the prefix is a tenant configuration row (app_cfg_main, ENTITY_CODE_PREFIX / the entity type) that no applet screen currently edits — the Supplier applet’s Supplier Code Prefix control exists in the source and is commented out — and the number comes from a counter keyed by bl_fi_mst_entity_hdr and the entity type (CUSTOMER_ID, SUPPLIER_ID, MERCHANT_ID, EMPLOYEE_ID) at tenant level. With prefix C and the default start, your first customer is C1000000. The engine checks the code is unused and moves on if it is not — so a tenant that imported customers with numeric codes can find its generated codes jump past them. A customer saved as a draft (TEMP) is not numbered until it is made active, and a code that is already filled in is never overwritten. Have the prefixes set before the first record; the Core module page puts this at step 8 of the setup order for that reason, and the Supplier applet page describes the same rule from the supplier side.

Job orders, consultations, appointments, pick-pack queues, price books and fixed-asset registers also number themselves from this table through a shared helper that keeps a number the user typed if it is unique and generates one otherwise.

What success looks like

Open the Sales Cash Bill listing for any branch, sort by Doc No (Tenant) descending, and look at the top three rows. You should see: consecutive tenant numbers in the millions with no prefix; a Doc No (Branch) on every FINAL row that is smaller than the tenant number and consecutive within that branch; and a blank branch number on any draft. Then open the Journal or the e-Invoice screen for one of them: its e-Invoice number, if it has one, is a different string altogether. If the tenant column is blank on a FINAL document, see the first common mistake.

Common mistakes

  1. “The tenant number is blank on a finalised document.” Number assignment runs right after the create and, if it fails (a database hiccup, a pool that could not be refilled), the error is logged and the document is saved anyway with a bl_fi_generic_doc_ext marker APP_SEQUENCE_COUNTER_PENDING still attached. Nothing retries. A platform administrator repairs it with the running-number endpoint (POST …/erp/running-number/sysadmin-ep with the document guids), which assigns the tenant number to any non-draft, non-discarded document without one and the company/branch numbers to any FINAL document without them.
  2. “Two branches share one sequence.” They share the tenant sequence by design; the per-branch sequence is in server_doc_3, hidden until Step 1. Asking for the tenant number to restart per branch is asking for a different product.
  3. “I set a START row and nothing changed.” A document of that type had already been numbered at that level, so the COUNTER row existed and START was never read. Move the COUNTER row instead, and expect the pooled numbers to be issued first.
  4. “I reset the counter and the next bill still has the old number.” The reset job moves the counter, not the pool. Up to 500 already-pooled numbers per counter go out before the reset shows.
  5. “Numbers on the listing are not in date order.” Correct and expected: a draft takes its tenant number when it is created, not when it is finalised, and a rolled-back save returns its number to the pool for the next document. The sequence is unique and gap-free, not chronological.

Related documentation