# Code Generation — Pattern Reference

> **Source of truth:** feature.json `analysis.entities[].codePattern`
> **Loaded by:** step-01 (analyze), step-03 (execute)
> **Shared by:** /business-analyse + /apex

---

## Code Strategies

| Strategy | Pattern | Example | Use Case |
|----------|---------|---------|----------|
| `sequential` | `{tenant}-{prefix}-{N:digits}` | `acme-emp-00001` | Employees, customers, projects |
| `timestamp-daily` | `{tenant}-{prefix}-{yyyyMMdd}-{N:3}` | `acme-inv-20260215-001` | Invoices, orders, receipts |
| `timestamp-minute` | `{tenant}-{prefix}-{yyyyMMddHHmm}-{N:3}` | `acme-ticket-202602151430-001` | Tickets, incidents, requests |
| `year-sequential` | `{tenant}-{prefix}-{yyyy}-{N:digits}` | `acme-ctr-2026-00001` | Contracts, policies, agreements |
| `uuid-short` | `{tenant}-{prefix}-{8hex}` | `acme-ref-a1b2c3d4` | Reference tables, categories, tags |
| `manual` | User-provided | `custom-code` | No auto-generation |

---

## Volume-to-Digits (x10 Rule)

Anticipate volume growth by multiplying estimated records by 10:

```
digits = max(4, ceil(log10(estimatedVolume * 10)))
```

| Estimated Volume | x10 | Digits | Max Capacity |
|-----------------|-----|--------|-------------|
| 100 | 1,000 | 4 | 9,999 |
| 1,000 | 10,000 | 5 | 99,999 |
| 10,000 | 100,000 | 6 | 999,999 |
| 100,000 | 1,000,000 | 7 | 9,999,999 |

**Default:** If no volume estimate is provided, use 5 digits (capacity: 99,999).

---

## Strategy Selection Heuristic

When the user does not specify a strategy, apply these defaults based on entity name:

| Entity name pattern | Default strategy | Rationale |
|--------------------|-----------------|-----------|
| Invoice, Order, Receipt, Bill, Delivery | `timestamp-daily` | Date context aids lookup and sorting |
| Ticket, Request, Incident, Support, Alert | `timestamp-minute` | High-frequency, time-sensitive items |
| Contract, Agreement, Policy, License, Subscription | `year-sequential` | Yearly lifecycle, annual numbering |
| Reference, Category, Tag, Type, Status, Config | `uuid-short` | Lookup tables, no sequential need |
| Employee, Customer, Client, Partner, Project, Product, Department | `sequential` | Standard business entities |
| Any other entity | `sequential` | Safe default |

---

## Code Regex

**Updated pattern** (supports hyphens for generated codes):

```
^[a-z0-9_-]+$
```

The previous pattern `^[a-z0-9_]+$` did NOT support hyphens, which are the default separator in all auto-generated codes. All validators MUST use the updated regex.

---

## Feature.json — codePattern Structure

Stored in `analysis.entities[].codePattern`:

```json
{
  "name": "Employee",
  "description": "Company employee",
  "attributes": [...],
  "relationships": [...],
  "codePattern": {
    "strategy": "sequential",
    "prefix": "emp",
    "includeTenantSlug": true,
    "separator": "-",
    "estimatedVolume": 1000,
    "digits": 5,
    "example": "acme-emp-00001"
  }
}
```

---

## Backend Architecture

### CodeStrategy Enum

```csharp
namespace SmartStack.Application.Common.Models.CodeGeneration;

public enum CodeStrategy
{
    Sequential,
    TimestampDaily,
    TimestampMinute,
    YearSequential,
    UuidShort,
    Manual
}
```

### CodePatternConfig Record

```csharp
namespace SmartStack.Application.Common.Models.CodeGeneration;

public record CodePatternConfig(
    CodeStrategy Strategy = CodeStrategy.Sequential,
    string Prefix = "item",
    bool IncludeTenantSlug = true,
    string Separator = "-",
    int EstimatedVolume = 1000,
    int Digits = 5);
```

### ICodeGenerator<TEntity> Interface

```csharp
namespace SmartStack.Application.Common.Interfaces.CodeGeneration;

/// <summary>
/// Generates unique codes for entities following configurable patterns.
/// </summary>
public interface ICodeGenerator<TEntity> where TEntity : class
{
    /// <summary>
    /// Generate the next unique code for the current tenant.
    /// Handles concurrency via retry on unique constraint violation (3 attempts).
    /// </summary>
    Task<string> NextCodeAsync(CancellationToken ct = default);
}
```

### CodeGenerator<TEntity> Implementation

```csharp
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SmartStack.Application.Common.Interfaces.Tenants;
using SmartStack.Application.Common.Interfaces.Persistence;
using SmartStack.Application.Common.Models.CodeGeneration;

namespace SmartStack.Infrastructure.Services.CodeGeneration;

public class CodeGenerator<TEntity> : ICodeGenerator<TEntity>
    where TEntity : class
{
    private readonly IExtensionsDbContext _db;
    private readonly ICurrentTenantService _currentTenant;
    private readonly CodePatternConfig _config;
    private readonly ILogger<CodeGenerator<TEntity>> _logger;
    private const int MaxRetries = 3;

    public CodeGenerator(
        IExtensionsDbContext db,
        ICurrentTenantService currentTenant,
        CodePatternConfig config,
        ILogger<CodeGenerator<TEntity>> logger)
    {
        _db = db;
        _currentTenant = currentTenant;
        _config = config;
        _logger = logger;
    }

    public async Task<string> NextCodeAsync(CancellationToken ct = default)
    {
        var tenantId = _currentTenant.TenantId
            ?? throw new UnauthorizedAccessException("Tenant context is required");
        var tenantSlug = _currentTenant.TenantSlug ?? "default";

        for (var attempt = 0; attempt < MaxRetries; attempt++)
        {
            try
            {
                var code = await GenerateCodeAsync(tenantId, tenantSlug, ct);
                return code;
            }
            catch (DbUpdateException ex) when (IsUniqueConstraintViolation(ex))
            {
                _logger.LogWarning(
                    "Code generation retry {Attempt}/{Max} for {Entity} tenant {TenantId}",
                    attempt + 1, MaxRetries, typeof(TEntity).Name, tenantId);

                if (attempt == MaxRetries - 1)
                    throw new InvalidOperationException(
                        $"Failed to generate unique code for {typeof(TEntity).Name} after {MaxRetries} attempts", ex);
            }
        }

        throw new InvalidOperationException("Code generation exhausted all retries");
    }

    private async Task<string> GenerateCodeAsync(
        Guid tenantId, string tenantSlug, CancellationToken ct)
    {
        var sep = _config.Separator;
        var parts = new List<string>();

        // 1. Tenant prefix (dynamic slug)
        if (_config.IncludeTenantSlug)
            parts.Add(tenantSlug.ToLowerInvariant());

        // 2. Entity prefix
        parts.Add(_config.Prefix);

        // 3. Date segment (for timestamp strategies)
        var now = DateTime.UtcNow;
        switch (_config.Strategy)
        {
            case CodeStrategy.TimestampDaily:
                parts.Add(now.ToString("yyyyMMdd"));
                break;
            case CodeStrategy.TimestampMinute:
                parts.Add(now.ToString("yyyyMMddHHmm"));
                break;
            case CodeStrategy.YearSequential:
                parts.Add(now.ToString("yyyy"));
                break;
        }

        // 4. Sequential number or UUID segment
        if (_config.Strategy == CodeStrategy.UuidShort)
        {
            parts.Add(Guid.NewGuid().ToString("N")[..8]);
        }
        else if (_config.Strategy != CodeStrategy.Manual)
        {
            var codePrefix = string.Join(sep, parts) + sep;
            var nextSeq = await GetNextSequenceAsync(tenantId, codePrefix, ct);
            parts.Add(nextSeq.ToString().PadLeft(_config.Digits, '0'));
        }

        return string.Join(sep, parts);
    }

    private async Task<int> GetNextSequenceAsync(
        Guid tenantId, string codePrefix, CancellationToken ct)
    {
        // Find the highest existing sequence matching the prefix
        var maxCode = await _db.Set<TEntity>()
            .Where(e => EF.Property<Guid>(e, "TenantId") == tenantId)
            .Where(e => !EF.Property<bool>(e, "IsDeleted"))
            .Where(e => EF.Property<string>(e, "Code").StartsWith(codePrefix))
            .Select(e => EF.Property<string>(e, "Code"))
            .OrderByDescending(c => c)
            .FirstOrDefaultAsync(ct);

        if (maxCode == null) return 1;

        // Extract the last segment (sequential number)
        var lastSegment = maxCode.Split(_config.Separator[0]).Last();
        return int.TryParse(lastSegment, out var seq) ? seq + 1 : 1;
    }

    private static bool IsUniqueConstraintViolation(DbUpdateException ex)
    {
        var message = ex.InnerException?.Message ?? "";
        return message.Contains("UNIQUE", StringComparison.OrdinalIgnoreCase)
            || message.Contains("duplicate", StringComparison.OrdinalIgnoreCase)
            || message.Contains("IX_", StringComparison.OrdinalIgnoreCase)
            || message.Contains("Cannot insert duplicate key", StringComparison.OrdinalIgnoreCase);
    }
}
```

---

## DI Registration Pattern

In `DependencyInjection.cs` (Infrastructure layer):

```csharp
// One registration per entity with auto-generated code
services.AddScoped<ICodeGenerator<Employee>>(sp =>
    new CodeGenerator<Employee>(
        sp.GetRequiredService<IExtensionsDbContext>(),
        sp.GetRequiredService<ICurrentTenantService>(),
        new CodePatternConfig(
            Strategy: CodeStrategy.Sequential,
            Prefix: "emp",
            IncludeTenantSlug: true,
            Separator: "-",
            EstimatedVolume: 1000,
            Digits: 5),
        sp.GetRequiredService<ILogger<CodeGenerator<Employee>>>()));
```

---

## Service Integration Pattern

When `codePattern.strategy != "manual"`, the service's `CreateAsync` method uses `ICodeGenerator<T>`:

```csharp
public class EmployeeService : IEmployeeService
{
    private readonly IExtensionsDbContext _db;
    private readonly ICurrentTenantService _currentTenant;
    private readonly ICodeGenerator<Employee> _codeGenerator;

    public EmployeeService(
        IExtensionsDbContext db,
        ICurrentTenantService currentTenant,
        ICodeGenerator<Employee> codeGenerator)
    {
        _db = db;
        _currentTenant = currentTenant;
        _codeGenerator = codeGenerator;
    }

    public async Task<EmployeeResponseDto> CreateAsync(
        CreateEmployeeDto dto, CancellationToken ct = default)
    {
        var tenantId = _currentTenant.TenantId
            ?? throw new UnauthorizedAccessException("Tenant context is required");

        // Auto-generate code (NOT from DTO)
        var code = await _codeGenerator.NextCodeAsync(ct);

        var entity = Employee.Create(
            tenantId: tenantId,
            code: code,
            name: dto.Name,
            createdBy: null);

        _db.Employees.Add(entity);
        await _db.SaveChangesAsync(ct);

        return MapToDto(entity);
    }
}
```

---

## CreateDto Changes (when auto-generated)

When `codePattern.strategy != "manual"`:
- **REMOVE** `Code` from `CreateDto` (auto-generated, not user-provided)
- **KEEP** `Code` in `ResponseDto` (returned to user after creation)
- **KEEP** `Code` in `UpdateDto` ONLY if code is mutable (rare)

```csharp
// CreateDto when strategy != manual — NO Code property
public record CreateEmployeeDto
{
    [Required]
    public string Name { get; init; } = string.Empty;
    // Code is auto-generated by ICodeGenerator — not in CreateDto
}

// ResponseDto — ALWAYS includes Code
public record EmployeeResponseDto
{
    public Guid Id { get; init; }
    public string Code { get; init; } = string.Empty;
    public string Name { get; init; } = string.Empty;
}
```

---

## Anti-Pattern: Count() + 1 (Race Condition)

```csharp
// WRONG — concurrent requests generate duplicate codes
var count = await _db.Employees
    .Where(e => e.TenantId == tenantId)
    .CountAsync(ct);
return $"emp-{(count + 1):D5}";

// CORRECT — use ICodeGenerator with retry on unique constraint violation
var code = await _codeGenerator.NextCodeAsync(ct);
```

**Why it's wrong:** `Count() + 1` causes **race conditions** — two concurrent requests get the same count and generate duplicate codes. The `ICodeGenerator` approach uses `OrderByDescending` on existing codes + retry on unique constraint violation for safe concurrency.

---

## Validator Regex Update

All entity validators MUST use the updated regex:

```csharp
// OLD (FORBIDDEN): ^[a-z0-9_]+$
// NEW (REQUIRED): ^[a-z0-9_-]+$
RuleFor(x => x.Code)
    .NotEmpty()
    .MaximumLength(100)
    .Matches("^[a-z0-9_-]+$")
    .WithMessage("Code must be lowercase alphanumeric with underscores and hyphens");
```

> **Note:** When `codePattern.strategy != "manual"`, the Code field is NOT in CreateDto, so the CreateValidator does NOT have a Code rule. The UpdateValidator still has it if Code is mutable.

---

## Edge Cases

1. **Concurrency:** The retry mechanism (3 attempts) handles typical SmartStack workloads (< 10 concurrent creates/second/tenant). For extreme volumes, consider DB sequences.
2. **Tenant slug null:** Falls back to `"default"` — should only happen in test/seed scenarios.
3. **Midnight/minute boundary:** For `timestamp-daily` and `timestamp-minute`, the sequence resets naturally within each time period. Two requests straddling midnight get different date prefixes.
4. **Backward compatibility:** Existing entities without `codePattern` are treated as `strategy: "manual"` (current behavior preserved).
