Sequence Number / Auto-Numbering Pattern
Overview
ISequenceCodeService.GetNextValue(sequenceName) returns the next value in a named, persisted counter. This is used to generate unique, human-readable IDs for instances — typically combined with date parts, type codes, or department codes to produce structured identifiers like REQ-24-03-007 or SVC-REQ-DEPT-2024-Mar-42.
When to Use
- Auto-generate a unique reference number when an instance is created (e.g., request number, contract number, docket ID).
- Assign a running sequence to an instance as part of a state transition (e.g., assign a formal ID only when submitted).
- Build structured IDs that encode year, month, department code, or type prefix alongside a counter.
Applicable Program Types
| Program Type | Interface | Typical Use |
|---|---|---|
| PreCreate Trigger | IPreCreateTriggerProgramAsync | Generate and set Name or a custom ID attribute before save |
| PromoteActionCommand | IPromoteActionCommandProgramAsync | Generate a formal ID at a specific state transition (e.g., on "Submit") |
Code Example — Generate Name on Create
Generates a structured name for a new service request using a named sequence and the current year/month.
using Prorigo.Protrak.API.Contracts;
using Prorigo.Protrak.API.Contracts.Enum;
using Prorigo.Protrak.API.Services;
using Prorigo.Protrak.Programs;
using System;
using System.Threading.Tasks;
namespace YourNamespace.Programs
{
/// <summary>
/// Type: Pre Create Trigger
/// Configured for: Type ServiceRequest
/// Trigger: Before service request instance creation
/// Summary: Generates a unique name in the format REQ-{YY}-{MM}-{NNN}.
/// </summary>
public class AutoGenerateRequestName : IPreCreateTriggerProgramAsync
{
public ISequenceCodeService SequenceCodeService { get; set; }
private readonly string ATTRIBUTE_NAME = "Name";
private readonly string SEQUENCE_NAME = "REQUEST_SEQ";
public async Task<ProgramResult> RunAsync(Instance instance)
{
var now = DateTime.Now;
var year = now.ToString("yy"); // "24"
var month = now.Month.ToString("D2"); // "03"
var seq = SequenceCodeService.GetNextValue(SEQUENCE_NAME); // e.g. "7"
var seqPadded = seq.PadLeft(3, '0'); // "007"
var name = $"REQ-{year}-{month}-{seqPadded}"; // "REQ-24-03-007"
instance.SetTextAttributeValue(ATTRIBUTE_NAME, name);
return new ProgramResult { IsSuccess = true };
}
}
}
Code Example — Generate Structured Reference ID on State Transition
Generates a multi-part reference ID at a promote action, incorporating department code, year, month, and sequence number. Skips if the ID has already been generated (idempotent).
/// <summary>
/// Type: Promote Action Command
/// Configured for: Type ServiceRequest
/// Trigger: On "Submit" action
/// Summary: Generates a structured reference ID: SVC-REQ-{DeptCode}-{YYYY}-{Mon}-{Seq}.
/// Skips if the ID has already been generated (idempotent).
/// </summary>
public class GenerateReferenceIdOnSubmit : IPromoteActionCommandProgramAsync
{
public IInstanceService InstanceService { get; set; }
public ISequenceCodeService SequenceCodeService { get; set; }
private readonly string ATTRIBUTE_TRACKING_ID = "TrackingId";
private readonly string ATTRIBUTE_DEPARTMENT = "OwnerDepartment";
private readonly string ATTRIBUTE_DEPT_CODE = "DepartmentCode";
private readonly string SEQUENCE_NAME = "ServiceRequestSeq";
private readonly string PREFIX_SVC = "SVC";
private readonly string PREFIX_REQ = "REQ";
public async Task<ProgramResult> RunAsync(Instance instance, string fromState, string toState, string actionName)
{
var inst = await InstanceService.GetInstanceAsync(instance.Id,
new[] { ATTRIBUTE_TRACKING_ID, ATTRIBUTE_DEPARTMENT });
var trackingId = inst.GetTextAttributeValue(ATTRIBUTE_TRACKING_ID);
if (string.IsNullOrEmpty(trackingId))
throw new Exception("Tracking ID is not set.");
// Idempotency: if the ID has already been formatted, skip
if (trackingId.Contains("-"))
return new ProgramResult { IsSuccess = true };
var deptRefs = inst.GetReferenceAttributeValue(ATTRIBUTE_DEPARTMENT);
if (deptRefs == null || deptRefs.Length == 0)
throw new Exception("Department is required.");
var dept = await InstanceService.GetInstanceAsync(deptRefs[0].Id, new[] { ATTRIBUTE_DEPT_CODE });
var deptCode = dept.GetPicklistAttributeValue(ATTRIBUTE_DEPT_CODE)?.FirstOrDefault()
?? throw new Exception("Department is missing a Department Code.");
var seq = SequenceCodeService.GetNextValue(SEQUENCE_NAME);
var month = DateTime.Now.ToString("MMM"); // "Mar"
var year = DateTime.Now.Year; // 2024
var refId = $"{PREFIX_SVC}-{PREFIX_REQ}-{deptCode}-{year}-{month}-{seq}";
inst.SetTextAttributeValue(ATTRIBUTE_TRACKING_ID, refId);
await InstanceService.UpdateInstanceAsync(inst, inst.Modified);
return new ProgramResult { IsSuccess = true };
}
}
ISequenceCodeService API
| Method | Signature | Returns |
|---|---|---|
GetNextValue | GetNextValue(string sequenceName) | string — the next counter value as a string |
- The sequence is identified by name (configured in the Protrak admin).
- The counter is per-tenant and persisted in the database.
- The return is a plain number string (e.g.,
"7","42"). Use.PadLeft(3, '0')to zero-pad if needed.
Idempotency Guard
Always guard against generating a second ID if the program is accidentally re-run (e.g., if the ID already contains the separator pattern):
if (trackingId.Contains("-")) return new ProgramResult { IsSuccess = true };
Common ID Formats
| Format | Example | Parts |
|---|---|---|
| TYPE-YY-MM-NNN | REQ-24-03-007 | Type prefix, 2-digit year, 2-digit month, zero-padded sequence |
| PREFIX-TYPE-DEPT-YYYY-Mon-N | SVC-REQ-ENG-2024-Mar-42 | Multiple prefixes, department code, full year, month abbreviation, sequence |
| ID-YYYY-ORG-USER | ID-001-2024-ORGNAME-JOHN | Base ID, year, org/user abbreviations |