Skip to main content

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 TypeInterfaceTypical Use
PreCreate TriggerIPreCreateTriggerProgramAsyncGenerate and set Name or a custom ID attribute before save
PromoteActionCommandIPromoteActionCommandProgramAsyncGenerate 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

MethodSignatureReturns
GetNextValueGetNextValue(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

FormatExampleParts
TYPE-YY-MM-NNNREQ-24-03-007Type prefix, 2-digit year, 2-digit month, zero-padded sequence
PREFIX-TYPE-DEPT-YYYY-Mon-NSVC-REQ-ENG-2024-Mar-42Multiple prefixes, department code, full year, month abbreviation, sequence
ID-YYYY-ORG-USERID-001-2024-ORGNAME-JOHNBase ID, year, org/user abbreviations