Master data management Β· features 286-305
πŸ›’οΈ SQL Server Β· MasterDataLabDB
πŸ”’

295Running Numbers

Automatic document numbers built from a pattern, reset every year / month / day / fiscal year.

Try it

Issue numbers

Next number per rule for today, the counters (one row per period) and the last numbers issued
TypePatternResetNext
CN {PREFIX}{YYYY}/{SEQ:4}{SUFFIX} YEARLY CN2026/0001-R
INV {PREFIX}{YYYY}{MM}-{SEQ:5} MONTHLY INV202611-00001
PO {PREFIX}-{FY}-{SEQ:5} FISCAL PO-FY2027-00001
PR {PREFIX}{SEQ:6} NEVER PR001000
QT {PREFIX}{YY}-{SEQ:4} YEARLY QT26-0001
RC {PREFIX}{BE}{MM}{SEQ:4}{SUFFIX} YEARLY RC2569110001/BKK
SO {PREFIX}{YYYYMMDD}-{SEQ:3} DAILY SO20261109-001
TypePeriodLast valueUpdated
No number issued yet - press "Issue next number".
None yet.
# Document type Name Pattern Reset Start at Step Status
7 CN Credit note {PREFIX}{YYYY}/{SEQ:4}{SUFFIX} YEARLY 1 1 Active ✏️
1 INV Tax invoice {PREFIX}{YYYY}{MM}-{SEQ:5} MONTHLY 1 1 Active ✏️
4 PO Purchase order (fiscal year) {PREFIX}-{FY}-{SEQ:5} FISCAL 1 1 Active ✏️
6 PR Purchase request (never resets) {PREFIX}{SEQ:6} NEVER 1000 1 Active ✏️
2 QT Quotation {PREFIX}{YY}-{SEQ:4} YEARLY 1 1 Active ✏️
3 RC Receipt (Buddhist year) {PREFIX}{BE}{MM}{SEQ:4}{SUFFIX} YEARLY 1 1 Active ✏️
5 SO Sales order (daily) {PREFIX}{YYYYMMDD}-{SEQ:3} DAILY 1 1 Active ✏️
Showing 1–7 of 7
πŸŽ“ Learn: how this master is built table running_number
  • Pessimistic lock: @Lock(PESSIMISTIC_WRITE) β†’ SELECT ... WITH (UPDLOCK, HOLDLOCK, ROWLOCK).
  • The counter row is created in its own transaction (TransactionTemplate REQUIRES_NEW).
  • Try "20 at once": 20 threads ask together and still get 20 different numbers.
Entity
domain/RunningNumber.java
Repository
repo/RunningNumberRepository.java
Definition
MasterRegistry: "running-numbers"
Page / form
web/MasterController.java
Try-it panel
templates/tools/running-number.html
REST API
POST /api/running-numbers/INV/next
SELECT r.doc_type, c.period_key, c.last_value
FROM running_counter c JOIN running_number r ON r.id = c.running_number_id
ORDER BY c.updated_at DESC;