Skip to content
bitzorcas
中EN

Reference

Numbering Conditional Rules and Segment DSL

Deep reference for TableName/Field selection, priorities, JSON conditions, RuleId, six segment types, dates, fiscal years, branch maps, and formatting traps.

Last updated

The rule DSL decides which business field receives a number, under what condition, and how its string is assembled. It is small and testable, but many configuration errors currently degrade silently.

1. Loading candidates

The query filters TableName, Field, IsActive, and IsEnabled. Tenant and soft deletion are applied by generated persistence adapters. Lower Priority runs first.

yesnonone

TableName + Field

load active/enabled rules

Priority ascending

Condition matches?

select first

next candidate

RuleNotFound

The selector has final “default fallback” code, but an empty condition already returns true in the loop. A default rule must be last.

2. Actual RuleId

Runtime RuleId is:

{TableName}-{Field}-{RuleName}

Setting rows must use exactly this value. A model comment still mentions only TableName+Field and is stale; do not build assets from it.

3. JSON conditions

Condition example
{
"field": "CaseType",
"op": "Eq",
"value": "Civil"
}

The enum uses JsonStringEnumConverter. Field lookup follows the EntityValues dictionary comparer, usually case-sensitive by default, while value comparisons ignore case.

4. Four operators

OperatorBehavior
Eqactual equals value, case-insensitive
Neactual differs from value, case-insensitive
Insplit value by comma, trim, and match
Containsactual contains value, case-insensitive

A missing property returns false. A null property becomes an empty string, so Eq "" can match null.

5. Condition failure semantics

Invalid JSON returns false without a log. A deserialized null unexpectedly returns true. Blank JSON also returns true. ConditionScope is stored and seeded but unused by selection or caching.

Validate before publishing a rule
public static class NumberingErrors
{
public static readonly Error ConditionInvalid =
Error.Validation("Numbering.Condition.Invalid", "Invalid condition");
}
// ① Deserialize and validate fields and operators before production storage.
var condition = JsonSerializer.Deserialize<SequenceRuleCondition>(json);
if (condition is null || string.IsNullOrWhiteSpace(condition.Field))
return Result.Failure(NumberingErrors.ConditionInvalid);
// ② Exercise the rule against representative business snapshots.
var matched = condition.IsSatisfiedBy(sampleEntityValues);
// ③ The empty default condition must carry the greatest Priority.
EnsureDefaultRuleIsLast(rules);

This is a desired publication validator; no current management entry point runs it.

6. Constant

Constant returns Value unchanged. An empty value becomes an empty segment, while string.Join can still leave separators. An unknown SectionType also parses as Constant, converting a spelling mistake into plausible output.

7. Date

Date defaults to yy, explicitly supports yy, yyyy, yyyy-MM, and yyyyMM, and passes other values to DateTime.ToString(format, InvariantCulture).

An invalid format can throw FormatException. There is no time-zone setting; behavior follows IAppClock.Now DateTime semantics.

8. FiscalYear

Fiscal years start on April 1: April–December uses the current two-digit year and January–March the previous one. A Date segment with IsFiscalYear=true also returns two digits and ignores DateFormat.

Source technical debt calls for configurable start month. Arbitrary corporate fiscal calendars are not supported today.

9. Branch

Branch reads a key from FieldSource and maps it through SubstitutionValue JSON. Missing FieldSource, invalid JSON, a missing key, or an unmapped value falls back to Value; empty Value becomes FD.

Every failure is silent and can collapse real branches into one counter prefix.

Branch map example
{
"SH": "SHA",
"BJ": "BJA",
"GZ": "GZA"
}

10. BusinessField

BusinessField reads FieldSource and calls ToString(). A missing field falls back to Value; a present null emits an empty string. There is no format, length, character, or sensitive-data validation.

Do not embed customer names, identity numbers, or other sensitive values in business numbers.

11. SequenceNumber

SequenceNumber formats with D{Length}; Length <= 0 defaults to six. StartValue exists in model, seed, and segment mapping, but GetNextSequenceNumberAsync never reads it, so an empty fact partition starts at one.

Multiple SequenceNumber segments repeat the same counter. There is no “exactly one counter segment” validator.

12. Separator and prefix

Assembler walks segments by Sort. Non-number outputs enter both prefix and full parts; number output enters only full. Each list is joined by Separator.

Deterministic output for the shipped case rule
// ① A fixed clock makes the Date segment deterministic.
var clock = new FakeClock(new DateTime(2026, 7, 15));
// ② CASE + yy + six digits, separated by hyphens.
var (full, prefix) = SequenceSegmentAssembler.AssembleFull(
segments,
new Dictionary<string, object?>(),
clock,
sequenceNumber: 1,
separator: "-");
// ③ full = CASE-26-000001; prefix = CASE-26.

13. Publication gates

Reject duplicate/negative Sort, zero or multiple SequenceNumber segments, unknown types, invalid date formats, invalid Branch JSON, blank field names, a non-last default rule, ambiguous equal priorities, and potential prefix/full values over 128 characters.

Neither the CSV reader nor runtime currently enforces this complete gate.

14. Inspection commands

Terminal window
# Complete pure condition and segment algorithms.
sed -n '1,260p' src/Framework/BitzOrcas.Application/Numbering/SequenceRuleSelector.cs
sed -n '1,300p' src/Framework/BitzOrcas.Application/Numbering/SequenceSegmentAssembler.cs
# StartValue and ConditionScope are carried but have no generation behavior.
rg -n "StartValue|ConditionScope" src/Platform/Numbering src/Framework/BitzOrcas.Application/Numbering -g '*.cs'

Module overview · Counters and compensation

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%